type
status
date
slug
summary
tags
category
icon
password
介绍
1. 什么是 React Query?
React Query 是一个用于 React 应用的数据管理库,它的目标是简化与远程数据源(如 REST API、GraphQL 等)的交互,同时提供强大的缓存和数据同步功能。这个库的核心思想是将数据获取、缓存管理和状态同步集成到一个易于使用的工具中,使开发者能够轻松地构建响应式、高性能的前端应用。
2. React Query 发展和历史?
React Query 由 Tanner Linsley 创建,他是一个活跃在前端社区的开发者,并且以开源社区中的贡献者身份而闻名。他的开源项目包括 React Table,这是一个用于构建表格组件的流行库。
React Query 最初于 2020 年初发布,当时它的目标是解决前端开发中常见的数据管理问题。在不断的迭代和社区贡献的支持下,React Query 迅速成长为一种受欢迎的数据管理解决方案。
发展历程关键里程碑:
3. 为什么选择 React Query?
我们都知道,前端一般有两种状态需要管理:
- 客户端状态
- 服务端状态
客户端状态也就是用户交互的中间状态,比如组件的 loading 态,控制 Dialog 的 isOpen 等,这类状态的特点是:
同步
更新
状态
由前端控制
状态
比较独立(例如:我们每个需要请求数据组件中都会有自己的 loading 态)
这类状态通常都是保存在组件的内部。状态如果想要跨层级传递,我们就需要用到 Context 或者一些
全局的状态管理库
,想Redux
、Mobx
等。服务端状态也就是我们从服务端请求的数据,把数据保存在组件内部作为组件中状态的一部分:
那么同样我们也可以将我们的数据(状态)保存在
Redux
等全局状态管理库
中。我们在实际项目中通常会按照上面的方式来做,但是这样会有一些问题:
- 需要重复处理请求中间状态,在组件内进行请求的过程中我们需要处理 loading 态、error 等中间状态
这种通用的中间状态我们可能需要在不同的组件中重复写很多次。
- 缓存的性质不同于状态
服务端状态应该被视为缓存:
- 通常以异步的形式请求、更新
- 状态由请求的数据源控制,不由前端控制
- 状态可以由不同的组件共享
还需要考虑到缓存失效以及缓存更新等问题,所以我们需要一个单独管理服务端状态的工具,React-Query 就是其中一个。
目前 React 项目中常用到的数据管理状态的库主要有 React-Query 和 SWR,它们的下载量对比如下:
可以看到,对比 SWR,React-Query 更适合复杂度更高的项目,因为它提供了更多的 hooks 帮助我们解决了许多传统的数据管理方案的痛点。它主要提供以下一些重要特性和优势:
- 自动缓存管理: React Query 会自动将从服务器获取的数据缓存起来,减少不必要的重复请求,从而提高了应用的性能和效率。
- 数据同步: 当多个组件同时使用相同的数据时,React Query 可以确保数据的同步性,以避免冗余的请求或不一致的数据状态。
- 错误处理: 它提供了强大的错误处理机制,使您能够轻松地处理网络请求失败或其他错误情况。
- 查询失效和自动重新获取: React Query 允许您定义查询失效条件,一旦这些条件满足,查询会自动重新获取数据,确保应用的数据始终是最新的。
- 插件系统: 您可以使用插件来扩展 React Query 的功能,以满足特定项目的需求。
- 简化的 API: React Query 提供了一个直观且易于使用的 API,使您能够快速上手,并在项目中轻松集成。
使用
1. 安装和配置
建议使用它们的 ESLint 插件:
2. 基本用法
提供全局上下文组件
React-Query 提供了可视化工具来帮助我们调试它:
devtools 需要我们先安装它
initialIsOpen
默认开启调试工具,默认情况下,React Query Devtools 仅在以下情况下 process.env.NODE_ENV === 'development'
包含在捆绑包中,因此您无需担心在生产构建期间排除它们。查询数据
几种常见的查询,在此列举项目中基本会用到几个查询 hooks。
普通查询:
查询失效及重新获取:以下六种触发条件会重新获取数据
- 组件挂载时
当组件首次加载,将会触发数据的获取。如果组件被卸载后再次被加载,此时也会触发数据的重新获取。
- 查询键改变时
当查询键改变时,将会自动触发数据的重新获取。
- 页面重新被聚焦
当用户把浏览器重新聚焦(比如在浏览器中打开了要调试的页面,之后切换到了 vscode 里面编辑代码,编辑完成后需要看下调试的页面效果,又切换到了浏览器),或者切换标签页(比如开了两个浏览器的选项卡,一个是百度,一个是你的页面,从百度的选项卡切换至你的页面)时,react-query 都会自动重新获取数据。
这个触发条件是默认开启的,如果希望关闭这个触发条件,可以把
refetchOnWindowFocus
选项设置为false
来禁止。- 网络重新链接
在当前用户断网重新联网后,react-query 将会重新获取数据。
这个触发条件同样是默认开启的。如果希望关闭这个触发条件,可以把
refetchOnReconnect
选项设置为false
来禁止。- 定时刷新
这是一个需要你自己配置的一个触发条件。当你在配置中设置
refetchInterval
为数字(代表 xxx 毫秒)时。无论此时数据是fresh(最新)
还是stale(老旧)
的缓存状态,react-query 都会在你设置的毫秒时间间隔内重新获取数据。`queryClient.invalidateQueries('exampleQueryKey')
手动触发标记为失效,或者使用refetch
。
注意:除了定时刷新外,其它的触发器,都需要状态是stale(陈旧)才可以触发。
并行查询:
清理缓存
在缓存状态处于
inactive(不活跃)
状态或者使用这个查询数据的组件卸载时,超过 5 分钟(默认情况下)后,react-query 将会自动清除该缓存。如果你希望自定义这个时间,你可以使用
cacheTime
配置,下面的例子中将cacheTime
设置为 0,实现的效果是,当查询数据在inactive
状态时,立即从缓存中删除。当在缓存中删除查询数据后,此时的表现和这个查询数据从未加载过一样,在查询数据从后端返回前,将看到加载状态,获取到数据后,将看到查询数据。
那么如果我们想要在其他组件中也想使用到这个缓存数据怎么做呢?react-query 提供了另外一个查询缓存的钩子函数
QueryCache
:它还提供了一个清理缓存并重新刷新的函数:
但是通常会使用下面这种方式来获取缓存数据:
无限查询:
适用于分页和滚动加载的情况
突变数据
如果想要使用缓存,和 useQuery 基本相同:
FAQ
1. 如何处理查询失败?
React Query 允许你在查询配置中使用
onError
选项来处理错误,例如:2. 如何在数据不再有效时重新获取数据?
使用
staleTime
选项来设置数据在失效之前可以保持的时间(以毫秒为单位):3. 如何在手动触发重新获取数据?
使用
queryClient.invalidateQueries
方法来手动标记查询为失效,从而触发重新获取:不要忽视缓存策略
React Query 提供了强大的缓存功能,但如果你不仔细配置缓存策略,可能会导致数据不会及时更新。确保了解和设置正确的
staleTime
、cacheTime
和 refetchInterval
等选项,以满足你的数据更新需求。不要滥用 Query Keys
每个查询都需要一个唯一的查询键(query key)。不要使用过于动态或不稳定的键,以免导致数据缓存问题。查询键应该稳定且与数据关联,通常是一个字符串或一个包含唯一标识符的数组。
避免在渲染循环中使用 useQuery
使用
useQuery
时要小心,不要在渲染循环中创建大量的查询,以免导致性能问题。通常,你应该在组件的顶层使用 useQuery
,并确保查询仅在需要时触发。使用 Query Devtools 进行调试
React Query 提供了一个强大的开发工具(Query Devtools)来帮助你调试查询和数据。确保在开发中使用它来更好地理解你的查询和缓存。
处理错误
不要忽视错误处理。确保你的查询配置中包含
onError
函数,以处理网络请求错误和其他异常情况。避免无限轮询
使用
refetchInterval
选项时要小心,以免触发无限轮询请求,导致服务器压力过大。合理设置轮询间隔以避免此问题。扩展插件
在 React Query 中,你可以使用插件来扩展库的功能,以满足特定项目的需求。插件是一种强大的方式,可以为 React Query 添加新的特性或修改现有特性的行为。
1. 创建插件
你可以创建一个自定义的插件函数,它接受一个
queryClient
对象作为参数,并在该对象上执行一些自定义操作。例如,下面是一个简单的插件示例,它在 queryClient
上添加了一个自定义方法:2. 注册插件
在你的应用程序中,你需要在创建
queryClient
实例后将插件注册到它上面。通常,在应用程序的入口文件中完成此操作:3. 使用插件
在你的组件中,你可以使用
queryClient
对象上的自定义方法或功能。例如:源码解析
React-Query的架构总体分为四大部分:QueryClient, QueryCache, Query, QueryObserver
所以我们也主要分析这几个的实现原理。
- 当我们初始化React项目时,会先全局生成一个QueryClient,用于提供封装好的请求方法,并使用React.context分享给App下所有的组件
- QueryClient上会挂载一个QueryCache,用来保存和管理所有的Query
- Query用于保存管理数据和请求状态,每个请求回来的数据都会new一个Query并保存在上面
- 每个useQuery钩子,都会给组件创建一个QueryObserver,用于监听Query中数据的变化,通知组件进行更新
React-Query主体结构图
QueryObserver通过组件观察Query示意图
QueryClient
实际项目中,开发者并不需要直接接触Query,常常通过QueryClient Class 或 useQuery Hook使用Query。
组件可以调用useQueryClient获取QueryClient对象。QueryClient是React.context管理的一个全局对象,开发者可以通过React.useContext共享QueryClient。useQueryClient hook仅仅做了简单包装。
它有三种常见的操作方法:
fetchQuery
prefetchQuery
invalidateQueries
fetchQuery:
从源码层面,可以发现QueryClient.fetchQuery会调用QueryCache.build尝试从QueryCache中读取Query缓存,从而实现复用曾经请求的数据。
prefetchQuery:
原理特征:实际上,QueryClient.prefetchQuery底层是调用QueryClient.fetchQuery,使用noop(空函数)处理fetchQuery返回的promise。因此,prefetchQuery不会返回数据,也不会抛出错误,适合用于预请求数据。
invalidateQueries:
QueryClient.invalidateQueries,会遍历调用所有匹配的Query.invalidate(),从而触发所有Query对应的QueryObserver.onQueryUpdate方法,最终调用QueryObserver.updateResult从最新的Query中读取数据。
Query
Query是react-query底层核心类,它负责网络数据请求、状态变化的处理、以及内存回收工作。
Query的网络请求的底层逻辑还是依赖Retryer实现的。
Query给Retryer指定fn(请求函数主体)、retry(重试次数)、retryDelay(重试延迟时间),以及一系列状态变化回调函数(比如onSuccess、onPause等)。
Query使用经典的reducer模式处理状态变化。reducer模式,其实我们并不陌生,Vuex、Redux等数据状态管理库都是通过reducer模式处理数据状态变化的。
reducer模式,离不开Dispatch、Action、和Reducer三个组成部分。
Query会在不同的状态变化回调函数中,调用Dispatch分发对应的Action,Dispatch最终会调用Reducer处理状态的变化。
Reducer函数,接受当前状态对象和Action对象,经过Switch结构处理后,返回新的状态。
所有使用Query的观察者,都要被添加到Query.observers数组中。通过Query.observers元素长度,可以判断Query是否处于活跃状态,当Query.observers.length === 0 表示没有任何Observer在使用Query对象,那么,Query就被视作不活跃的状态,已经具备被垃圾回收的之一条件。
一旦Observer观察Query对象,必须调用Query.addObserver方法,把Observer添加到Query.observers数组中。除此之外,还会停止垃圾回收机制。
与Query.addObserver相反,Query.removeObserver负责把observer从Query.observers中移除,同时调度垃圾回收机制,回收Query占用的内存。
QueryCache
Query-Cache用于保存和管理Query对象 , 提供了对Query的增删查方法
Query-Cache在new QueryClient()的时候被创建并挂载
QueryObserver
该类用于跟踪和管理数据查询的状态、结果和更新
每次组件重新渲染时,都会调用
observer.getOptimisticResult
获取数据,并且重新传递defaultedOptions
配置对象。每次调用
getOptimisticResult
,都会用defaultedOptions.queryHash
做key,从QueryCache缓存中寻找Query对象。总结
通过使用 React-Query(或 SWR)这样的数据请求库,可以将服务端状态从全局状态中解放出来。
这为我们带来很多好处:
- 使用通用的 hook 处理请求中间状态
- 多余请求合并
- 针对缓存的更新/失效策略
- Redux 等「全局状态管理方案」可以更专注于「前端中间状态」处理
- Author:GDYG
- URL:https://gdyg5.top/article/react-query
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!