React Query
官方项目:https://github.com/TanStack/query
官方文档:https://tanstack.com/query/latest/docs/framework/react/overview
和主流上层封装库比较(包括 swr):https://tanstack.com/query/latest/docs/framework/react/comparison?from=reactQueryV3
使用 react-query
安装
yarn add react-query
配置全局实例
在 react 根节点渲染处如此配置 react-query 的全局实例:
import { QueryClientProvider, QueryClient } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
// ↓ 初始化全局实例,通过该全局实例可以传入默认配置,这里本文不做详述
const queryClient = new QueryClient()
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
{/* ↓ 主应用节点 */}
<App />
{/* ↓ 可视化开发工具 */}
<ReactQueryDevtools />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root')
);
可视化 Devtools
在上文我们配置了一个 react-query 可视化开发工具,他可以在我们屏幕指定的位置显示 react-query 的图标,打
开面板后可查看所有请求的状态和请求情况:
对于每个请求的 key、新鲜度、是否过期,以及每个请求的配置,都可以在此处查看,十分强大。该工具不会被打包到生
产环境,可以放心使用。
官方介绍:https://tanstack.com/query/latest/docs/framework/react/devtools?from=reactQueryV3
基础入门
看一个 react-query 的简单 demo:
import { useState } from 'react';
import { useQuery } from 'react-query'
function App() {
const [status, setStatus] = useState(false)
const request = ({ queryKey }) => {
// 为了模拟实际中 api 的时长随机性,随机 1.5s 或 3s 后得到响应
const time = Math.random() > .5 ? 3000 : 1500
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(time)
}, time)
})
}
const {
isLoading,
isFetching,
isError,
data,
refetch
} = useQuery([status, 'ss', 2], request)
return (
<>
{isLoading && <div>Loading...</div>}
{isFetching && <div>Fetching...</div>}
{isError && <div>Error</div>}
{data && <div>{data}</div>}
<button onClick={() =>{
// 同 swr,可以通过改变 key 重新获取数据
setStatus((pre) => !pre)
}}>click</button></>
);
}
健全的 loading 态
真正给出 loading 参数:isLoading 和 isFetching,两者的区别是,isLoading 是在 “硬” 加载时才会为
true,isFetching 是在每次请求时为 true,那么 isFetching 我们能通俗易懂的理解,就是每次请求时当做
loading 嘛。
那什么是 isLoading 的 “硬” 加载?其实 “硬” 加载就是没有缓存时的加载
了解了 react-query 缓存机制,我们就明白了,原来 isLoading 只会在第一次加载页面挂载组件,此时没有
cache 时为 true,之后每次再去获取新数据,也不会变为 true。
loaidng 态怎么用
所以 isLoading 的使用场景比较适合第一次加载页面,在为 true 时显示加载页,当有了第一次数据,后续一致使用
isFetching 去给组件显示 loading 态。当然,在第一次没有缓存的 “硬” 加载时,isFetching 也是为 true
的,我们没有加载页的场景下,只使用 isFetching 作为 loading 态就足够了。
健壮的请求参数
和 swr 不同,
react-query 将传入的 key 一律放置到了请求函数的第一个参数对象的 queryKey 键上,也就是:
// 此时 key 标识为 [status, 'ss', 2]
useQuery([status, 'ss', 2], request)
// 传入到请求函数的第一个参数对象中的 queryKey 键上
// 也就是 queryKey 就为 [status, 'ss', 2]
const request = ({ queryKey }) => {}
这就比较友好了,一次性拿到所以 key 值,那问题就来了,为什么还要放到 queryKey 中,因为 react-query 给请
求函数第一个参数对象中还放置了一个键叫 pageParam ,用于无限分页查询
请求掌握能动性强(取消请求)
上文 中提到了 swr 对每个请求的掌控性不强,在这方面,react-query 通过一个全局实例,来实现了主动对任意 key 的请求操作。
我们可以通过 useQueryClient 方法得到全局 QueryClient 实例,在该实例上有很多主动 api,可以充分管理整个 react-query 的请求。
提到 swr 想要主动重请求,要设立新的 state ,在 react-query 如何解决?
看如上代码我们发现通过引入全局实例 queryClient 并在点击按钮时调用了 refetchQueries 方法重新加载了指定 key 的请求数据,等等,为啥我的 key 是 [status, 'ss', 2] 你却使用了 status ? 默认情况下,refetchQueries 传参会模糊匹配,并重新获取所有符合匹配的 api,这在多个请求中非常有用
匹配规范可见文档
如果想精确匹配,可传入第二个参数 options 对象指定 exact 为 true
queryClient.refetchQueries([status, 'ss', 2], { exact: true })
再度简化:对于单个 react-query 查询,他自带一个叫 refetch 的返回函数,在任意地方你想重新获取数据就可以重新获取:
const {
// ......
refetch
} = useQuery([status, 'ss', 2], request)
return (
<>
// ......
<button onClick={() =>{
// ↓ 直接调用 refetch 来重新获取
refetch()
}}>click</button></>
);
在数据一致性问题上,swr 和 react-query 都是默认忽略旧的请求,最终得到的 data 都是最新一次请求的结果,保
证数据的一致性,可 swr 有昂贵请求不能取消的问题,而 react-query 在全局实例上提供了自定义 cancel 方法
const queryClient = useQueryClient()
return (
<>
// ......
<button onClick={() =>{
// 取消指定 key 的请求
queryClient.cancelQueries(key)
}}>click</button></>
);
仅仅在上层生效
这个 cancelQueries 方法只能阻断 isFetching 等 loading 态
不能侵入 axios ,因为 axios 的取消请求只能用 cancel token ,所以你要主动给 axios 返回的 promise 上
挂载一个 cancel 方法,届时 react-query 才会去调用这个 promise 上的 cancel 真正取消请求
import { CancelToken } from 'axios'
const query = useQuery('todos', () => {
// Create a new CancelToken source for this request
const source = CancelToken.source()
const promise = axios.get('/todos', {
// Pass the source token to your request
cancelToken: source.token,
})
// Cancel the request if React Query calls the `promise.cancel` method
promise.cancel = () => {
source.cancel('Query was cancelled by React Query')
}
// 这里返回请求的promise
// react query会自动使用上面的cancel属性
return promise
})