查询 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { useQuery } from '@tanstack/react-query' function App ( ) { const getUserInfo = async ( ) => { const data = await axios ('/api/useInfo' ) return data } const userInfo = useQuery ({ queryKey : ['useData' ], queryFn : getUserInfo }) }
这个唯一的key 值,将在内部用于重新获取、缓存和在整个项目中共享该查询的信息
useQuery
的返回结果包含了你需要用到的绝大部分信息,这也正是使用react-query的一个好处,可以提高开发效率
1 2 3 4 5 6 const { isLoading, isError, isSuccess, data, error, status } = useQuery ({ queryKey : ['useData' ], queryFn : getUserInfo })
首先是结果中会返回一些比较重要的状态:
布尔值isLoading
或者 status === 'loading'
表示查询暂时还没有数据
布尔值isError
或者 status === 'error'
表示查询遇到了错误
布尔值isSuccess
或者 status === 'success'
表示查询成功,数据可用
可以看出 status 和 isLoading、isError、isSuccess的作用是差不多的,关键在于你习惯于使用谁来进行判断
然后是一些重要的信息:
error
如果查询处于isError
状态,可以获取到错误的具体信息
data
如果查询处于isSuccess
状态,可以获取到请求的数据
下面是一个简单的例子,来整体演示使用useQuery
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import { useQuery } from '@tanstack/react-query' function App ( ) { const getUserInfo = async ( ) => { const data = await axios ('/api/useInfo' ) return data } const { isLoading, isError, isSuccess, data, error, status } = useQuery ({ queryKey : ['useData' ], queryFn : getUserInfo }) if (status === "loading" ) { return <span > Loading...</span > ; } if (status === "error" ) { return <span > Error: {error.message}</span > ; } return ( <div > <div > userName: {data.userName}</div > <div > age: {data.age}</div > </div > ) }
查询键值 queryKey 在react-query内部基于queryKey来管理查询缓存
queryKey必须是一个数组 ,对数组内部的结构没有过多的限制,该数组可以简单的由一个或多个字符串构成,也可以是包含许多嵌套对象的数组。
最简单的形式
1 2 3 4 useQuery ({ queryKey : ['userData' ], ... })
复杂的形式
当查询需要更多的信息来唯一的描述数据时,数组可以是 字符串 加上 任意数量的可序列化对象的形式
常见的场景有:需要传递参数来进行查询
1 2 3 4 5 6 7 8 9 10 11 let userId = 'xxx' useQuery ({ queryKey : ['useInfo' , userId], ... }) useQuery ({ queryKey : ['xxxx' , { type : 'xxx' }] })
注意: queryKey的散列是确定的,即 顶层数组中 各个key不同的排列顺序 会被认为是不同的查询键值
1 2 3 4 5 6 7 useQuery ({ queryKey : ['todos' , status, page], ... })useQuery ({ queryKey : ['todos' , page, status], ...})useQuery ({ queryKey : ['todos' , { status, page }], ... })useQuery ({ queryKey : ['todos' , { page, status }], ...})
查询函数 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 2 3 4 5 6 7 8 9 10 useQuery ({ queryKey : ['useData' , useId], queryFn : getUserData }) const getUserData = async ({ queryKey } ) => { const [ _key, useId ] = queryKey const data = await fetch ('/api/useData' + useId) return data }
抛出和处理错误
为了使 React Query 确定查询错误,查询函数的错误必须抛出或返回rejected Promise。查询函数中引发的任何错误都将被持久化在查询的error状态中
并行查询 并行查询即并行的执行多个查询,或者说同时执行的查询
如果需要并行查询的数量较少且固定,可以使用手动的并行查询
1 2 3 4 5 6 7 function App () { const usersQuery = useQuery ({ queryKey : ['users' ], queryFn : fetchUsers }); const teamsQuery = useQuery ({ queryKey : ['teams' ], queryFn : fetchTeams }); const projectsQuery = useQuery ({ queryKey : ['projects' ], queryFn : fetchProjects }); ... }
在 React 的 suspense 模式下使用 React Query 时,这种并行模式不起作用。 因为第一个查询将在内部抛出 Promise,并且将在其他查询运行之前挂起组件。 此时建议使用 useQueries
hook
使用useQueries
进行动态并行查询
如果需要执行的查询数量不固定,即在每次渲染之间都会变化,那就不能进行手动查询了
useQueries
接受一组作为查询配置的对象,并以数组形式返回查询的结果:
1 2 3 4 5 6 7 8 9 10 function App ({ users } ) { const userQueries = useQueries ({ queries : users.map ((user ) => { return { queryKey : ["user" , user.id ], queryFn : () => fetchUserById (user.id ), }; }), }); }
有依赖的查询有依赖的查询 或者说 按顺序排列的查询,即当前查询是否执行(或何时执行)依赖于前一个查询的结果
可以使用enable
配置项来告诉query何时可以运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const { data } = useQuery ({ queryKey : ["user" , email], queryFn : getUserByEmail, }); const userId = data?.id ;const { data } = useQuery ({ queryKey : ["projects" , userId], queryFn : getProjectsByUser, enabled : !!userId, });
QueryClient 使用QueryClient
可以创建一个query客户端,来与query缓存联系起来
1 2 3 4 5 6 7 8 9 10 import { QueryClient } from '@tanstack/react-query' const queryClient = new QueryClient ({ defaultOptions : { queries : { staleTime : Infinity , }, }, })
QueryClient
可传入一个对象,其中包含三个参数(三个参数都是可选的):
defaultOptions
:为使用该client的所有查询(query)和修改(mutation)设置默认的配置项
queryCache
:该client所连接的query缓存
mutationCache
:该client所连接的mutation缓存
配置项
refetchOnWindowFocus
如果用户在短暂离开窗口后回来时,数据被标记为过时的,react-query会在后台自动请求新的数据
可以使用该配置项在全局或者单个查询中禁用该功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const queryClient = new QueryClient ({ defaultOptions : { queries : { refetchOnWindowFocus : false , }, }, }) useQuery ({ queryKey : ["useData" ], queryFn : getUserData, refetchOnWindowFocus : false , })
enabled
可以为单个查询配置enabled = false
来禁用自动查询
当enabled = false
时:
如果查询已经缓存了数据,将以status === 'success'
进行初始化
如果查询没有缓存数据,将以status === 'loading'
进行初始化
该查询不会在挂载时自动获取数据、不会在后台重新获取数据
将忽略客户端的invalidateQueries
和refetchQueries
调用
从useQuery
返回的 refetch
可用于手动触发查询以进行数据获取
1 2 3 4 5 6 7 8 const { isError, data, error, refetch } = useQuery ({ queryKey : ["useData" ], queryFn : getUserData, enabled : false , }) <button onClick={() => refetch ()} >手动触发查询</button>
永久性的禁用查询并不是你使用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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import { QueryClient } from 'react-query' ;const queryClient = new QueryClient ();const fetchUserById = async (userId ) => { const response = await fetch (`api/users/${userId} ` ); if (!response.ok ) { throw new Error ('Network response was not ok' ); } return response.json (); }; const handleUserLinkClick = (userId ) => { queryClient.fetchQuery (['user' , userId], () => fetchUserById (userId)); navigate (`/users/${userId} ` ); }; const UserDetails = ({ userId } ) => { const { data, isLoading, error } = useQuery ( ['user' , userId], () => fetchUserById (userId), { staleTime : Infinity , } ); if (isLoading) { return <span > Loading...</span > ; } if (error) { return <span > Error: {error.message}</span > ; } return ( <div > <h1 > {data.name}</h1 > {/* ... 其他用户信息 */} </div > ); };
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 2 3 4 5 6 7 8 9 10 import { useQuery, useQueryClient } from "@tanstack/react-query" const queryClient = useQueryClient ()queryClient.invalidateQueries () queryClient.invalidateQueries ({ queryKey : ["userData" ] })
对于传入queryKey使查询失效的精确度,也有多种方式进行控制
传入特定的(或者完整)的queryKey
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 queryClient.invalidateQueries ({ queryKey : ["userData" , { userName : "easy code sniper" }], }) const userQuery = useQuery ({ queryKey : ["userData" , { userName : "easy code sniper" }], queryFn : getUserData, }) const userQuery = useQuery ({ queryKey : ["userData" ], queryFn : getUserData, })
使用exact
配置,表示只想使指定的queryKey对应的查询失效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 queryClient.invalidateQueries ({ queryKey : ["userData" ], exact : true , }) const userQuery = useQuery ({ queryKey : ["userData" ], queryFn : getUserData, }) const userQuery = useQuery ({ queryKey : ["userData" , { userName : "easy code sniper" }], queryFn : getUserData, })
自定义更精细化的查询失效
可以将 predicate
函数传递给invalidateQueries
方法。 此函数将从查询缓存中接收每个Query实例,并允许你返回 true 或 false 来确定是否使该查询无效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 queryClient.invalidateQueries ({ predicate : (query ) => query.queryKey [0 ] === "userData" && query.queryKey [1 ]?.age >= 18 , }); const userQuery = useQuery ({ queryKey : ["userData" , { age : 20 }], queryFn : getUserData, }); const userQuery = useQuery ({ queryKey : ["userData" , { age : 10 }], queryFn : getUserData, });
修改 Mutations
对于mutations的定义,在官方文档中是这么说的: “mutations are typically used to create/update/delete data or perform server side-effects.”
即:用于创建、删除、更新数据或者执行服务器命令等操作
你可以在组件或者自定义hook中使用 useMutation
来修改数据
简单示例:修改用户的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function App ( ) { const mutation = useMutation ({ mutationFn : (userInfo ) => { return axios.post ("/updateUserInfo" , userInfo); }, }); return ( <div > {mutation.isLoading ? ( "updating ..." ) : ( <> {mutation.isError ? ( <div > An error occurred: {mutation.error.message}</div > ) : null} {mutation.isSuccess ? <div > updated!</div > : null} <button onClick ={() => { mutation.mutate({ userName: 'easy code sniper' }); }} > 修改用户信息 </button > </> )} </div> ); }
由上面的例子可以看出,你可以通过调用mutation
方法来给mutationFn传入参数
useMutation
和查询一样也会返回一些状态和信息:
状态:
布尔值isLoading
或者 status === 'loading'
表示修改正在进行
布尔值isError
或者 status === 'error'
表示修改遇到了错误
布尔值isSuccess
或者 status === 'success'
表示修改成功,数据可用
信息:
error
如果修改处于isError
状态,可以获取到错误的具体信息
data
如果修改处于isSuccess
状态,可以获取到数据
注意: mutate
函数是一个异步函数,在React16及以前版本,你不能在事件回调中直接使用它。你需要将mutate包装在另一个函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const CreateTodo = ( ) => { const mutation = useMutation ({ mutationFn : (event ) => { event.preventDefault () return fetch ("/api" , new FormData (event.target )) }, }) return <form onSubmit ={mutation.mutate} > ...</form > } const CreateTodo = ( ) => { const mutation = useMutation ({ mutationFn : (formData ) => { return fetch ("/api" , formData) }, }) const onSubmit = (event ) => { event.preventDefault () mutation.mutate (new FormData (event.target )) } return <form onSubmit ={onSubmit} > ...</form > }
副作用 useMutation
最便利,也是最有用的功能可能就在于它能定义一些副作用配置,这些配置允许在其生命周期的任何阶段快速而简单地产生副作用。
一个最常见的例子就是,在修改数据之后能自动的重新获取最新的数据(如果你经历过一些开发,那你一定会对这个功能感到兴奋🚀~~)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 useMutation ({ mutationFn : updateUserInfo, onMutate : (variables ) => { }, onError : (error, variables, context ) => { }, onSuccess : (data, variables, context ) => { }, onSettled : (data, error, variables, context ) => { }, })
除了在useMutation
中可以配置这些副作用项外,还可以在调用mutate
函数时配置组件特定的副作用,支持的配置项包括:onSuccess
、onError
和 onSettled
1 2 3 4 5 6 7 8 mutate (userInfo, { onSuccess : (data, variables, context ) => { }, onError : (error, variables, context ) => { }, onSettled : (data, error, variables, context ) => { }, })
注意: 如果组件在修改完成之前就被卸载了,那使用mutate配置的组件特定的副作用将不被运行
修改导致的查询失败 在开发中非常常见的是,当应用中的一个对数据的修改成功时,很有可能在你的应用中有相关的查询需要作废,并需要重新获取数据来解释修改所产生的新变化
假设我们调用const mutation = useMutation({ mutationFn: updateUserInfo })
对用户信息进行了修改,那么势必导致现在渲染的数据是过时的。我们希望所有对userInfo的查询都失效,并重新获取最新的数据
我们可以使用useMutation
的副作用onSuccess
配置 和QueryClient的invalidateQueries
函数来实现
1 2 3 4 5 6 7 8 9 10 11 import { useMutation, useQueryClient } from "@tanstack/react-query" ;const queryClient = useQueryClient ();const mutation = useMutation ({ mutationFn : updateUserInfo, onSuccess : () => { queryClient.invalidateQueries ({ queryKey : ["userData" ] }) }, })
通过修改的数据更新查询内容 当在处理更新某些数据时,新的数据往往会在更新的响应中自动返回,我们可以利用修改函数返回的对象,并使用 Query Client 的 setQueryData
方法立即用新数据更新现有的查询,而不是去触发新的数据获取,浪费对已有数据的网络调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const queryClient = useQueryClient ();const mutation = useMutation ({ mutationFn : updateUserInfo, onSuccess : (data ) => { queryClient.setQueryData (["userData" , { userName : 'cqy' }], data); }, }); mutation.mutate ({ userName : 'cqy' , age : 22 , }); const { status, data, error } = useQuery ({ queryKey : ["useData" , { userName : 'cqy' }], queryFn : fetchTodoById, });
useInfiniteQuery useInfiniteQuery
钩子用于处理无限滚动或分页场景,它可以用来逐页加载数据,并且可以无缝地集成更多数据加载到现有数据集中。
参数
useInfiniteQuery
接受以下参数:
queryKey
queryFn
options (可选): 一个配置对象,包含以下属性:
getNextPageParam
: 一个函数,用于从最后一页的数据中获取下一页的 pageParam
getPreviousPageParam
: 一个函数,用于从第一页的数据中获取上一页的 pageParam
其他 React Query 提供的所有配置选项,如 staleTime, cacheTime, onSuccess, onError 等
getNextPageParam
getNextPageParam
用于定义如何从获取到的数据中提取分页参数,以便加载下一页的数据。
getNextPageParam
函数接收两个参数:
lastPage
: 当前查询返回的最后一页数据。
allPages
: 当前已经加载的所有页面组成的数组。
函数的返回值应该是一个值,这个值将作为下一个 pageParam
参数传递给 queryFn
函数以获取下一页数据。
示例:
1 2 3 4 5 6 7 8 9 const getNextPageParam = (lastPage, allPages ) => { return lastPage.nextPage ; }; const getNextPageParam = (lastPage, allPages ) => { return lastPage.nextCursor ; };
getPreviousPageParam
getPreviousPageParam
用于获取前一页数据,在实现双向无限滚动或分页时特别有用。
getPreviousPageParam
函数接收两个参数:
firstPage
: 当前查询返回的第一页数据。
allPages
: 目前已经加载的所有页面数据的数组。 这个函数的返回值应该是一个标识,表示用于 queryFn
函数获取前一页数据时所需的 pageParam
示例:
1 2 3 4 5 6 7 8 9 const getPreviousPageParam = (firstPage, allPages ) => { return firstPage.prevPage ; }; const getPreviousPageParam = (firstPage, allPages ) => { return firstPage.prevCursor ; };
返回值
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import { useInfiniteQuery } from 'react-query' ;const fetchArticles = async ({ pageParam = 1 } ) => { const res = await fetch (`/api/articles?page=${pageParam} ` ); return res.json (); }; const ArticlesList = ( ) => { const { data, error, fetchNextPage, hasNextPage, isFetchingNextPage, status, } = useInfiniteQuery ('articles' , fetchArticles, { getNextPageParam : (lastPage, pages ) => { if (lastPage.nextPage ) { return lastPage.nextPage ; } else { return undefined ; } }, }); if (status === 'loading' ) return <p > Loading...</p > ; if (status === 'error' ) return <p > Error: {error.message}</p > ; return ( <> {data.pages.map((page, i) => ( <React.Fragment key ={i} > {page.articles.map(article => ( <p key ={article.id} > {article.title}</p > ))} </React.Fragment > ))} <div > <button onClick ={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage} > {isFetchingNextPage ? 'Loading more...' : hasNextPage ? 'Load More' : 'Nothing more to load'} </button > </div > </> ); };
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)。