使用 react-query swr 之前,必看

4,630 阅读6分钟

你也许正在调研一个技术栈, 关于调研,我有一个自己的观点

比看一个东西解决了什么问题更有价值的是看他带来了什么问题

解决方法没有银弹, 他解决了一个问题势必带来另一个问题, 而这些问题他不会直接告诉你,掘金此类文章也很少, 我调研一个东西主要还是看issue,看discuss, 看他解决不了的问题, 其次才是寻找该库的最佳实践

好, 正文开始

react-query和swr的定位都是用作服务端状态管理的,但是它的使用方式很容易让人觉得它是数据请求的hook,千万别建立这样的惯性思维,否则你会遇到无尽的麻烦

在掘金 很多推荐swr的文章,例子里都有useState的身影,这完全是错误示范,想想看,真能这样用,为什么他们的官网文档,几乎不写useState的代码作为式例?

还是那句话,解决方法没有银弹,每个东西都有自己的使用场景,别滥用

何时使用react-query swr ?

先上结论:

  1. 用作服务端状态管理时,都可以,更推荐swr,因为size更小, 比如在 nuxtjs, remix 里使用
  2. 用作客户端请求库时,只推荐ahooks的useRequest

为什么不推荐使用它们处理客户端状态 ?

react-query 踩坑记录

场景:

  • queryKey 变了, 不想请求?

    • 比如对象数组作为key, 当数组顺序改变,并不想请求
    • swr 和 react-query, 做不到
    • ahooks 不使用 queryKey 作为依赖, 所以想请求就请求,不想请求就不请求
  • queryKey 没变, 想请求?

    • 比如实时数据, 过一段时间,点击请求按钮,因为 key 没变, 所以不会请求
    • ahooks 不使用 queryKey 作为依赖, 所以想请求就请求,不想请求就不请求
    • 使用refetch 强刷? 有翻页参数时会导致请求两次:
page =1 & form不变, refetch() // 请求一次, ok
page =2 & form 不变, refetch() // 查询使业务需要重置page为1, 导致请求两次:page1一次, page2一次
page =2 & form 改变, refetch() // page 和 form 没有在 useEffect时修改时, 两次: refetch一次, 新参数一次; page 和 form 至少有一个是在 useEffect 里修改时, 至少三次,refetch一次, useEffect 外一次, useEffect里一次

如果你不care多请求,也行,毕竟不会导致竞态问题

  • 批量接口重新请求 ?

    • 比如切换全局筛选器, 想把整个页面的接口都刷新一遍
    • react-query 的 queryClient.invalideQueries() 仅支持 get请求, swr没测试过, 且所有参数都需要加入queryKey, 比如 headers里有一个 gameId, 当改变 gameId 时并放入localStorage里, 调用invalideQueries() 从localStorage里取数据,这时header里的gameId 可能是新的也可能是旧的
    • ahooks 没测试过
    • react-query 和 swr 的单接口fetch
    • 最后推荐 zustand 来做
  • 单接口重新请求 ?

    • react-query refetch() 不支持传参,所以你得维护一个是否触发请求的状态,queryKey 等同于 useEffect, 但是react推荐不要把事件处理程序里得逻辑放在uesEffect
    • swr refetch() 支持传参,但是参数必须同时也在queryKey里,不然无效, 实属神经病设计
    • ahooks refetch() 支持传参, 没有 queryKey 的限制,你可以在事件处理程序里调用refetch()

看到这,你会发现 useRequest 和普通的fetch 没什么两样, 除了它有一个方便的refetch,和处理了竞态问题, 以及减少了维护loading,error 状态, 不过对我来说, 多写几行 loading,error 状态代码没什么大不了,所以我还是使用 fetch

  • 解决竞态问题, 但remix作者说,做竞态处理的项目很少,大多数时候 loading 时禁用一下按钮足够了
  • 乐观更新,这玩意,我没遇到这样的场景,不做评价

还是那句话 如无必要,勿增实体,如果以上你都不care,强烈推荐ohfetch,随用随调

名词解释:

按照我国教育的惯例, 应该在文章开头就写,但是我不care纸上谈兵的理论, 所以在这普及一下

什么是服务端状态?

这是服务端渲染相关领域的概念,指在服务端渲染组件时,发起网络请求,获取到数据,这份数据称为服务端状态,有个特点: 该请求的请求参数不由用户交互(useState)产生的,在纯spa页面里,这个概念也适用,当一个请求无参数,或者全来自于prop,我们可以把这个组件放到服务端去渲染,因此也称为服务端状态

什么是客户端请求?

在客户端渲染期间发起的请求。即spa应用里的所有请求,可以把它们大致分为两类

  1. 请求参数由useState提供或部分提供,这主要是由用户交互修改,这部分请求不适合用swr去接管,这是我的血泪经验,同时也是react-query作者的意思
  2. 没有状态就是第二类,你可以使用swr去处理

服务端状态 相对于 客户端状态 真正的区别 ?

  1. 无参请求 /userInfo
  2. 请求参数只来自prop /detail(props.id)
为什么推荐在 nuxtjs, remix 里,才使用 swr react-query ?

nuxtjs 和 remix 支持服务端组件, 如果你有很多的服务端组件,并且需要共享状态, 那么使用它俩就真的很优雅

在spa应用中,很不推荐, 唯一的用处是:获取 userInfo时, 免去存到context的代码, 很多事件,甚至使用localStorage就够用了, 推荐 mantine的 useLocastorage, 它做了双向同步, 当 storage 改变, 会重新render所有使用了该组件的state, 同时state 改变也会同步到storage;大多数时候已经足够了, 再复杂一点, 可以上更优雅的 zustand

如果你在服务端状态里使用, 我也可以提供一点注意事项:

  • 务必设置 staleTime:Infinity, 这样才能达到一个全局状态管理的目的,否则返回值用作hook的依赖时,会造成无限循环
  • 读缓存时,使用useQuery,不要使用 queryClient.getCache, 因为在按需使用时, 很难保证使用useQuery获取首次数据一定在getCache之前, 这会加重心智负担,这也是完全可避免的

swr的优点

  • 减少 context 或者 props 的传递
  • 性能更好, 用到时才请求
  • 多组件共享,减少请求数
    • 比如多个菜单页面都会有城市下拉选择筛选, 那么只需要请求一次即可
    • 尤其适用于开发table编辑功能, 需要行内联动切换多份数据源给Select组件使用时, 你可以完全把数据请求封装在select组件里,只会请求一次, 否则, 1. 你要么在顶层组件遍历请求所有情况, 2.要么封装请求到select组件,每一行都会请求
  • 多页面共享,减少请求数
    • SPA 应用, 切换菜单只会卸载组件, 因此多个菜单里都使用了这份数据时,可以减少请求数

何时不要使用 react-query(swr)?

  • 你的请求参数有来自于用户交互(useState)
    • 比如筛选表单,搜索表单,翻页 table 等, 虽然文档介绍了支持翻页, 但最好别用, 缺点大于优点, 什么乐观更新,都是鸡肋, 远远大于你为了使用它而付出的东西, 这也是作者的意思 yes because that's client state