在 react-query 入门文章:
《 React Api 请求最佳实践 react-query 3 使用教程( 比 swr 更好用更强大 )》
中,我们学会了 react-query 的基本使用。
我们注意到两个点:
react-query 有全局的实例,可以管理全局的请求和数据,缓存就是在全局管理的,不论在哪个组件,存在 任意性
缓存是可以自由设定过期时间,当新鲜度 staleTime
为 Infinity
永不过期时,每次获取数据无需等待,是瞬间的,那么加上无限时间的缓存 cacheTime
,数据将永久存在于内存中,且获取是瞬间同步的,具有 持久性
好家伙,这不就是全局状态管理吗。
先使用 useQuery
造一个空请求,无限的过期时间和永远保持新鲜,让数据存在于全局:
import { useQuery } from 'react-query'
// 通过这个 key ,外界也可以用全局实例去做一些操作
export const KEY = '__key'
// 初始数据
export const initialData = {
key1: 'value1',
key2: 'value2'
}
// 暂且给我们的全局状态管理 hooks 起名为 useData
export const useData = () => {
const { data } = useQuery(KEY, {
initialData,
cacheTime: Infinity,
staleTime: Infinity,
refetchOnWindowFocus: false
})
return {
data
}
}
注意,使用 initialData
选项将自动将该初始值推入缓存,会使 useQuery
第一次就具有缓存,而该缓存将在全局永不过期。
第二步,造一个设定值的函数 setData
:
import { useCallback } from 'react'
import { useQuery, useQueryClient } from 'react-query'
export const useData = () => {
// 拿到全局实例
const queryClient = useQueryClient()
// ......
const setData = useCallback(
(newData) => {
return queryClient.setQueryData(KEY, (pre) => ({
...pre,
...newData,
}))
},
[queryClient]
)
return {
data,
setData
}
}
这个就很简单了,届时我们通过:
setData({ key2: 'new value2' })
就可以直接改变 key2
的值了,无需理会 key1
。
等等,这不是 context 变了个样子吗?毕竟全局实例 queryClient
是通过全局 context 透传的。我们用全局状态管理 redux 还要在意的就是能否执行异步任务!
提到异步任务,那就到了一个 api 请求库的强项了,为异步而生的 useMutation
:
import { useCallback } from 'react'
import { useQuery, useQueryClient, useMutation } from 'react-query'
// 模拟一个 1.5s 的异步任务
const sleep = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: 'result',
})
}, 1500)
})
}
export const useData = () => {
// ......
// 这是一个异步任务
const asyncApi = useMutation(
// 第一个参数为异步函数
async () => {
const res = await sleep()
return res.data
},
// 第二个参数是一个配置对象,在其中处理生命周期
{
// ↓ 请求前的处理
onMutate: (variables) => {
// ↑ variables 为执行该异步任务传入的参数
// ...
// ↓ 返回值将挂载到整个生命周期的 context 上下文中
return { key: 'value' }
},
// ↓ 请求成功的处理
onSuccess: (data, variables, context) => {
// ↓ data 为异步函数的返回结果,即上文的 res.data 也就是 'result'
const newData = setData({
key2: data,
})
// ↓ variables 为执行该异步任务传入的参数
console.log('variables: ', variables)
// ↓ context 为贯穿于生命周期的上下文对象,我们在请求前的生命周期 onMutate 中给其挂载了 { key: 'value' }
console.log('context: ', context.key)
},
// ↓ 发生错误时进入的生命周期
onError: (err, variables, context) => {},
// ↓ 无论结果如何最后都会进入的生命周期
onSettled: (data, error, variables, context) => {}
}
)
return {
data,
setData,
asyncApi
}
}
如此一来,异步任务也得到解决,通过 asyncApi.mutate
同步方法或 asyncApi.mutateAsync
异步方法均可调用该函数。
关于 useMutation
的 options 选项更多内容详见:useMutation
其实到这里你会发现,useMutation
的可操作性很广,正如我们在之前一篇文章中所说的,useMutation
是一个主张性很强的 hooks ,他将我们的异步任务带入 react-query 的管辖,从而在生命周期产生各种副作用和交互的可能。
你甚至可以给 useMutation
配置唯一的 key 以便在全局任何地方操作他;
无论在 onSuccess
还是 asyncApi.data
操作返回值,都由你来决定;
你可以通过全局实例的 setMutationDefaults 方法动态调整该 mutation 的 options,让生命周期都是动态的;
从全局实例的 getMutationCache 方法获取每次请求的缓存得到无限利用可能;
你可以在 onMutate
有预见性的更新数据,当 onError
存在错误时回滚,达到 事务管理 ;
异步请求自带 asyncApi.isLoading
请求 loading 态,自带请求失败重试 3 次(可配置)
等等无限自定义可能…
谈了这么多,react-query 确实功能很丰富,但是他的全局状态管理终究是 context 为基础,既然是 context 就要避免不必要的渲染,context 需要考虑的思维在此处同理。
如果你使用 react-query 去做全局状态管理,请经常思考:
哪些组件需要 memo
优化?
哪些组件需要做抽离调整?(从 children 传入不必渲染的组件以达到 props.children
不变和非当前 renderLanes 渲染线条件避免渲染)
另外,阿里 hox 也是一个很不错的全局状态管理 + 逻辑的工具,只是其 model 将导致你写很多模板代码,并且没有 react-query 在异步处理上的灵活,但你无需产生 context 的焦虑。