查询 queries
对于query的定义,在官方文档中是这么说的:
“A query is a declarative dependency on an asynchronous source of data that is tied to a unique key” 翻译过来是:查询是对绑定到唯一键的异步数据源的声明性依赖项
我对于query的理解是:将一个唯一的key(unique key) 与 一个获取数据的方法 进行绑定
你可以在组件中或者hook中,使用 useQuery
来订阅一个查询
useQuery
至少需要接受两个参数:
- key:该查询的唯一键值
- fn:一个返回Promise的函数,能解析出数据或者抛出错误(即具体的请求数据的函数)
1 | import { useQuery } from '@tanstack/react-query' |
这个唯一的key值,将在内部用于重新获取、缓存和在整个项目中共享该查询的信息
useQuery
的返回结果包含了你需要用到的绝大部分信息,这也正是使用react-query的一个好处,可以提高开发效率
1 | // 接上面例子 |
首先是结果中会返回一些比较重要的状态:
- 布尔值
isLoading
或者status === 'loading'
表示查询暂时还没有数据 - 布尔值
isError
或者status === 'error'
表示查询遇到了错误 - 布尔值
isSuccess
或者status === 'success'
表示查询成功,数据可用
可以看出 status 和 isLoading、isError、isSuccess的作用是差不多的,关键在于你习惯于使用谁来进行判断
然后是一些重要的信息:
error
如果查询处于isError
状态,可以获取到错误的具体信息data
如果查询处于isSuccess
状态,可以获取到请求的数据
下面是一个简单的例子,来整体演示使用useQuery
1 | import { useQuery } from '@tanstack/react-query' |
查询键值 queryKey
在react-query内部基于queryKey来管理查询缓存
queryKey必须是一个数组,对数组内部的结构没有过多的限制,该数组可以简单的由一个或多个字符串构成,也可以是包含许多嵌套对象的数组。
- 最简单的形式
1 | useQuery({ |
- 复杂的形式
当查询需要更多的信息来唯一的描述数据时,数组可以是 字符串 加上 任意数量的可序列化对象的形式
常见的场景有:需要传递参数来进行查询
1 | // 根据 userId 查询数据 |
注意:
queryKey的散列是确定的,即 顶层数组中各个key不同的排列顺序会被认为是不同的查询键值
1 | // 以下两个的查询键值不相等,因为顶层数组中各key的排列顺序不同 |
查询函数 queryFn
官方文档对queryFn的定义:A query function can be literally any function that returns a promise. The promise that is returned should either resolve the data or throw an error.
即:queryFn可以是任何形式的函数,必须返回一个promise,并且返回的promise应该 给出数据 或者 抛出错误
- queryFn的参数
queryFn默认接受一个queryFunctionContext
参数,该参数是一个对象,主要包含以下属性:
- queryKey:即整个queryKey数组
- pageParam:在 无限查询 中会使用到,包含查询当前页所使用的参数
- signal:用作 查询取消
- meta:可以填写任意关于该查询的额外信息
由上可知,queryKey不仅可以用于唯一的标识查询,也可以作为参数传递给queryFn
1 | useQuery({ |
- 抛出和处理错误
为了使 React Query 确定查询错误,查询函数的错误必须抛出或返回rejected Promise。查询函数中引发的任何错误都将被持久化在查询的error状态中
并行查询
并行查询即并行的执行多个查询,或者说同时执行的查询
- 如果需要并行查询的数量较少且固定,可以使用手动的并行查询
1 | function App () { |
在 React 的 suspense 模式下使用 React Query 时,这种并行模式不起作用。 因为第一个查询将在内部抛出 Promise,并且将在其他查询运行之前挂起组件。 此时建议使用
useQueries
hook
- 使用
useQueries
进行动态并行查询
如果需要执行的查询数量不固定,即在每次渲染之间都会变化,那就不能进行手动查询了
useQueries
接受一组作为查询配置的对象,并以数组形式返回查询的结果:
1 | function App({ users }) { |
有依赖的查询
有依赖的查询 或者说 按顺序排列的查询,即当前查询是否执行(或何时执行)依赖于前一个查询的结果
可以使用enable
配置项来告诉query何时可以运行:
1 | // 先执行这个query来拿到user的数据 |
QueryClient
使用QueryClient
可以创建一个query客户端,来与query缓存联系起来
1 | import { QueryClient } from '@tanstack/react-query' |
QueryClient
可传入一个对象,其中包含三个参数(三个参数都是可选的):
defaultOptions
:为使用该client的所有查询(query)和修改(mutation)设置默认的配置项queryCache
:该client所连接的query缓存mutationCache
:该client所连接的mutation缓存
配置项
refetchOnWindowFocus
如果用户在短暂离开窗口后回来时,数据被标记为过时的,react-query会在后台自动请求新的数据
可以使用该配置项在全局或者单个查询中禁用该功能
1 | // 全局 |
enabled
可以为单个查询配置enabled = false
来禁用自动查询
当enabled = false
时:
- 如果查询已经缓存了数据,将以
status === 'success'
进行初始化 - 如果查询没有缓存数据,将以
status === 'loading'
进行初始化 - 该查询不会在挂载时自动获取数据、不会在后台重新获取数据
- 将忽略客户端的
invalidateQueries
和refetchQueries
调用 - 从
useQuery
返回的refetch
可用于手动触发查询以进行数据获取
1 | // 手动触发查询 |
永久性的禁用查询并不是你使用react-query的理由,你可能更多的需要进行惰性查询
retry
当useQuery
查询失败时,如果该查询的请求未达到最大连续重试次数(默认 3 次),那么react-query将自动重试该查询。
可以在全局或者单个查询上配置重试逻辑
retry = false
将禁用重试 &&retry = true
将无限次重试retry = 5
设置为一个数字,表示最大重试次数retry =(failureCount,error)=> ...
允许基于请求失败的原因进行自定义逻辑
QueryClient的一些api
queryClient.fetchQuery
该方法是一个异步的方法,用于触发查询并将结果缓存,通常用于预加载数据。
参数
fetchQuery
接受的参数和useQuery
相同,即 queryKey、queryFn、option(可选)
返回值
调用fetchQuery
将返回一个promise对象,包含了请求错误的信息 或者 成功时的数据
当调用fetchQuery
时会执行如下步骤:
- 检查缓存中是否已有与提供的 queryKey 对应的缓存数据
- 如果缓存中有数据,并且数据是新鲜的,那么
fetchQuery
将返回这些数据。 - 如果缓存中没有数据,或者数据已过时,那么将执行 queryFn,并将结果数据放入缓存中。
使用示例
演示用户在导航到用户详情页时,使用fetchQuery
预加载用户数据
1 | import { QueryClient } from 'react-query'; |
queryClient.prefetchQuery
该方法是一个异步的方法,用于在后台提前获取数据并将其存储在缓存中,以便在未来某个时刻需要时能够立即使用
prefetchQuery
的工作方式与fetchQuery
大致相同,但prefetchQuery
更关注于数据的预加载,它通常不返回数据,只是单纯地将数据预加载到缓存中。同时,prefetchQuery
通常在数据需要之前调用,而不是在渲染组件时调用
queryClient.getQueryData
该方法是一个同步方法,用于返回已存在的查询的缓存数据,如果没有缓存数据则返回undefined
参数
只需要传入 queryKey 即可
返回值
如果缓存存在则返回数据,否则返回undefined
使用示例
const data = queryClient.getQueryData(queryKey)
更多
其他更多的queryClient
api 可以前往官网查看
主动查询失败 Query Invalidation
查询会在过时之后自动重新查询,但是在很多时候,由于修改了某些数据,你能明确的知道数据已经是过时的了(即使它还没有到默认的过时时间)。
这个时候可以调用QueryClient
的invalidateQueries
方法来明确的告诉react-query数据已经过时了,并重新查询新的数据
简单的例子:
1 | import { useQuery, useQueryClient } from "@tanstack/react-query" |
对于传入queryKey使查询失效的精确度,也有多种方式进行控制
- 传入特定的(或者完整)的queryKey
1 | queryClient.invalidateQueries({ |
- 使用
exact
配置,表示只想使指定的queryKey对应的查询失效
1 | queryClient.invalidateQueries({ |
- 自定义更精细化的查询失效
可以将 predicate
函数传递给invalidateQueries
方法。 此函数将从查询缓存中接收每个Query实例,并允许你返回 true 或 false 来确定是否使该查询无效
1 | queryClient.invalidateQueries({ |
修改 Mutations
对于mutations的定义,在官方文档中是这么说的:
“mutations are typically used to create/update/delete data or perform server side-effects.”
即:用于创建、删除、更新数据或者执行服务器命令等操作
你可以在组件或者自定义hook中使用 useMutation
来修改数据
简单示例:修改用户的信息
1 | function App() { |
由上面的例子可以看出,你可以通过调用mutation
方法来给mutationFn传入参数
useMutation
和查询一样也会返回一些状态和信息:
状态:
- 布尔值
isLoading
或者status === 'loading'
表示修改正在进行 - 布尔值
isError
或者status === 'error'
表示修改遇到了错误 - 布尔值
isSuccess
或者status === 'success'
表示修改成功,数据可用
信息:
error
如果修改处于isError
状态,可以获取到错误的具体信息data
如果修改处于isSuccess
状态,可以获取到数据
注意:
mutate
函数是一个异步函数,在React16及以前版本,你不能在事件回调中直接使用它。你需要将mutate包装在另一个函数中
1 | // 在React16及之前的版本,这将无法正常工作 |
副作用
useMutation
最便利,也是最有用的功能可能就在于它能定义一些副作用配置,这些配置允许在其生命周期的任何阶段快速而简单地产生副作用。
一个最常见的例子就是,在修改数据之后能自动的重新获取最新的数据(如果你经历过一些开发,那你一定会对这个功能感到兴奋🚀~~)
1 | useMutation({ |
除了在useMutation
中可以配置这些副作用项外,还可以在调用mutate
函数时配置组件特定的副作用,支持的配置项包括:onSuccess
、onError
和 onSettled
1 | mutate(userInfo, { |
注意: 如果组件在修改完成之前就被卸载了,那使用mutate配置的组件特定的副作用将不被运行
修改导致的查询失败
在开发中非常常见的是,当应用中的一个对数据的修改成功时,很有可能在你的应用中有相关的查询需要作废,并需要重新获取数据来解释修改所产生的新变化
假设我们调用const mutation = useMutation({ mutationFn: updateUserInfo })
对用户信息进行了修改,那么势必导致现在渲染的数据是过时的。我们希望所有对userInfo的查询都失效,并重新获取最新的数据
我们可以使用useMutation
的副作用onSuccess
配置 和QueryClient的invalidateQueries
函数来实现
1 | import { useMutation, useQueryClient } from "@tanstack/react-query"; |
通过修改的数据更新查询内容
当在处理更新某些数据时,新的数据往往会在更新的响应中自动返回,我们可以利用修改函数返回的对象,并使用 Query Client 的 setQueryData
方法立即用新数据更新现有的查询,而不是去触发新的数据获取,浪费对已有数据的网络调用
1 | const queryClient = useQueryClient(); |
useInfiniteQuery
useInfiniteQuery
钩子用于处理无限滚动或分页场景,它可以用来逐页加载数据,并且可以无缝地集成更多数据加载到现有数据集中。
参数
useInfiniteQuery
接受以下参数:
- queryKey
- queryFn
- options (可选): 一个配置对象,包含以下属性:
getNextPageParam
: 一个函数,用于从最后一页的数据中获取下一页的 pageParamgetPreviousPageParam
: 一个函数,用于从第一页的数据中获取上一页的 pageParam- 其他 React Query 提供的所有配置选项,如 staleTime, cacheTime, onSuccess, onError 等
getNextPageParam
getNextPageParam
用于定义如何从获取到的数据中提取分页参数,以便加载下一页的数据。
getNextPageParam
函数接收两个参数:
lastPage
: 当前查询返回的最后一页数据。allPages
: 当前已经加载的所有页面组成的数组。
函数的返回值应该是一个值,这个值将作为下一个 pageParam
参数传递给 queryFn
函数以获取下一页数据。
示例:
1 | const getNextPageParam = (lastPage, allPages) => { |
getPreviousPageParam
getPreviousPageParam
用于获取前一页数据,在实现双向无限滚动或分页时特别有用。
getPreviousPageParam
函数接收两个参数:
firstPage
: 当前查询返回的第一页数据。allPages
: 目前已经加载的所有页面数据的数组。
这个函数的返回值应该是一个标识,表示用于queryFn
函数获取前一页数据时所需的pageParam
示例:
1 | const getPreviousPageParam = (firstPage, allPages) => { |
返回值
useInfiniteQuery
返回一个对象,其中包含以下属性:
status
: 查询的状态(’loading’, ‘error’, ‘success’, ‘idle’)。data
: 包含每一页数据的数组。error
: 查询失败时的错误对象。fetchNextPage
: 函数,用于获取下一页数据。fetchPreviousPage
: 函数,用于获取上一页数据。hasNextPage
: 布尔值,表示是否还有下一页数据。hasPreviousPage
: 布尔值,表示是否还有上一页数据。isFetchingNextPage
: 布尔值,表示是否正在获取下一页数据。isFetchingPreviousPage
: 布尔值,表示是否正在获取上一页数据。isFetching
: 布尔值,表示是否正在进行查询(包括初始查询和后续的分页查询)。isLoading
: 布尔值,表示是否正在进行初始查询。isRefetching
: 布尔值,表示是否正在重新获取数据。- 其他 React Query 钩子通常返回的属性,如 refetch, remove 等。
data
useInfiniteQuery
返回的data与常规的useQuery
不同,因为它需要处理多个“页”的数据,而不是单个数据集。
data
是一个对象,它包含以下属性:
pages
: 一个数组,其中的每一个元素代表了每一页加载的数据。这些数据按照加载的顺序排列。例如,data.pages[0]
是第一页的数据,data.pages[1]
是第二页的数据,依此类推。pageParams
: 这个数组与pages
数组有相同的长度,包含了获取每一页数据时 queryFn 函数使用的参数。通常这些参数是由getNextPageParam
或getPreviousPageParam
函数提供的。
使用示例
下面是一个使用 useInfiniteQuery
来实现无限滚动加载文章列表的例子:
1 | import { useInfiniteQuery } from 'react-query'; |
useMutation
useMutation
钩子是用来处理异步逻辑(如创建、更新或删除数据)的,这些逻辑会引起数据的变化。与useQuery
和useInfiniteQuery
不同的是,useMutation
不是用来获取数据的,而是用来修改数据
参数
useMutation
接受以下参数:
mutationFn
(必须): 一个函数,它执行异步逻辑(比如API调用)。这个函数接受你传递给mutate函数的变量。options
(可选): 一个配置对象,可以包含如下属性:onMutate
: 在 mutationFn 执行之前调用的函数,用于执行乐观更新或返回回滚函数的数据。onSuccess
: 当 mutationFn 成功完成时调用的函数。onError
: 当 mutationFn 执行失败时调用的函数。onSettled
: 无论 mutationFn 成功还是失败都会调用的函数。- 以及其他选项
返回值
useMutation
返回一个对象,其中包含以下属性和方法:
mutate
: 一个函数,你可以用它来触发异步逻辑(mutationFn)的执行。mutateAsync
: 类似于mutate,但是返回一个Promise,可以用于async/await。data
: mutationFn成功解析的数据。error
: 如果mutationFn抛出错误,这里会包含错误对象。isLoading
: 如果mutationFn正在执行,这里会是true。isSuccess
: 如果mutationFn成功完成,这里会是true。isError
: 如果mutationFn执行失败,这里会是true。status
: mutation的状态(idle、loading、success、error)。