背景
在项目中,通常都需要跟服务端进行异步的数据交互,这包括 查询 和 变更。
以一个简单的列表查询为例,我们通过 axios
去请求服务端的列表数据:
OK! 数据已经成功的取到了,也就是我们完成了跟服务端的一次查询交互了。现在我们来尝试更进一步,在 React
中可以通过实现一个 Hooks
把查询做的更优雅一点:
Perfect!? 并没有!
我们遗漏了非常重要的 请求状态 的处理,包括异常和请求进行中的情况,让我们继续完善 useListQuery
:
以上,就是一个典型的请求处理的场景,为了实现它,我们写了近 30 行代码 ... 用来刷代码行数也是极其不错的 ... ?
那难道没有一种标准的请求处理模式吗?当然有!接下来我们进入正题,来看看 ReactQuery
的解决方案。
请求处理模式
初识 ReactQuery
的第一印象,通常都源于它提供的开箱即用的 Query
和 Mutation
的 API.
这就是 ReactQuery 能力的第一重境界 -- 请求处理
它通过 useQuery
、useMutation
等 Hooks API, 提供了一系列标准的请求处理模式。
查询
那么首先来看看 ReactQuery 是怎么处理我们的列表请求的:
useQuery
通常包含两个参数:
- 一个能唯一标识这个请求的
Query key
- 一个真正执行请求并返回数据的异步方法
ReactQuery
的缓存策略是基于这个 key 来实现的。key 值除了字符串外,还可以是一个数组或者对象:
useQuery('list', ...)
useQuery(['list'], ...)
// 数组或对象作为 key 时通常都包含查询条件
useQuery(['list', 1], ...)
userQuery(['list', {
page: 1
}])
useQuery({
type: 'list',
page: 1
})
Query key 唯一的要求就是可以被序列化。
而对于请求方法,useQuery
要求是一个 then-able
的函数即可,在我们日常使用情况中,通常指代的就是返回 Promise, 而 Promise 的返回值即请求的响应数据。
变更
与 useQuery
类似,ReactQuery 也提供了数据变更的 Hooks API:
useMutation
的参数通常包含一个真正执行请求的异步方法,返回值第一项为逻辑完备的 mutate
异步方法,在按钮点击后,可以通过调用 mutate
提交数据以及状态的处理。
ReactQuery 在请求处理上给我们提供了一个标准的处理方案,但是它的角色远不止请求库这么简单。在官方文档的 Overview 中作者就给了一个定位:
全局服务端状态管理
接下来,进入 ReactQuery 的第二重境界 -- 全局服务端状态管理。
Global Server State Management
什么是 Server State
首先,我们需要知道什么是服务端状态。在无意识的行为中,我们通常都将所有的组件渲染所需要的数据都放在一起管理,比如放在 State 中或者通过 Redux 这类状态管理库来管理。
然而,我们再来斟酌一下我们的数据,是不是通常都有明显的来源特征:
- 列表数据、详情数据等通过调接口由服务端提供的数据;
- 选中状态、折叠状态这类由客户端来维护的状态;
基于数据的来源,我们就可以将组件渲染所需要的状态分为服务端状态和客户端状态。
ReactQuery 的状态管理
ReactQuery 就将我们所有的服务端状态维护在全局,并配合它的缓存策略来执行数据的存储和更新。借助于这样的特性,我们就可以将所有跟服务端进行交互的数据从类似于 Redux
这样的状态管理工具中剥离,而全部交给 ReactQuery 来管理。
ReactQuery 会在全局维护一个服务端状态树,根据 Query key 去查找状态树中是否有可用的数据,如果有则直接返回,否则则会发起请求,并将请求结果以 Query key 为主键存储到状态树中。
缓存
ReactQuery 的缓存策略使用了 stale-while-revalidate
. 在 MDN 的 Cache Control 中对这个缓存策略的解释是:
在 ReactQuery 中的体现是,可以接受状态树中存储的 stale
状态数据, 并且会在缓存失效、新的查询实例被构建或 refetch 等行为后执行更新状态。
关于 ReactQuery 缓存的处理过程,官方给了一个详细的示例
ReactQuery 还能解决这些问题
刷新列表状态
日常开发工作中经常需要处理在添加、删除或者编辑后刷新列表的数据。为了实现这个行为,我们通常需要将列表数据的状态抽取到列表和详情的父组件中去管理:
然而,在模式上,列表数据的只是 List 组件的数据源,应该收敛到 List 组件中去管理,而不应该放在父组件中。那如果这样的话,兄弟组件之间如何通信以达到更新列表数据的目的?似乎问题变得越来越复杂 ...
不怕!ReactQuery 来帮我们解这个问题~ 前面我们说过 ReactQuery 维护的是一个全局的状态树,那既然是全局的,问题不就简单了:
ReactQuery 提供了 queryCache.invalidateQueries
可以直接指定某个 Query key 的缓存数据失效,这样 ReactQuery 就会在后台自动重新拉取最新的数据并更新到状态树中,这样列表组件中就渲染最新的数据了!完美!
usePaginatedQuery 和 useInfiniteQuery
除了基础的 useQuery 外,ReactQuery 还提供了 usePaginatedQuery
和 useInfiniteQuery
, 分别来处理 分页 和 无限加载 两个细分场景下的查询。
通过上述 API, 可以让我们在代码中免去维护类似于 分页 这样的客户端状态。
const [pagination, setPagination] = React.useState({
currentPage: 1,
pageSize: 10,
totalSize: 0
});
更多
除了本文中提到的这些基础能力外,ReactQuery 还提供了 Prefetching
、SSR
、Optimistic Updates
等高级特性。
另外,除了我们上面用到的 queryCache.invalidateQueries
, queryCache 还包含很多 API 来方便我们做一些手动的的状态操作。
重要提示
- 如果您看了本篇文章并开始使用 ReactQuery了,请一定不要忽视官方文档中的 Guides & Concepts: Important Defaults 中的
Important Defaults
, 了解 ReactQuery 有那些默认的行为设置,这会帮助您构建更健壮的应用。 - 本文编写时基于 ReactQuery@2 版本,目前 ReactQuery@3 版本已发布
我们是阿里巴巴 CCO 技术部团队,希望有更多的同学加入跟我们一起做更多有意义的事情。技术栈限 React
,有 Typescript
经验者更佳,Base 南京或杭州。有意者联系:henry.lx@alibaba-inc.com
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!