React-Query
是一个基于hooks的数据请求库。React-Query
中的Query
指一个异步请求的数据源。通过使用React-Query
(或SWR
)这样的数据请求库,可以将服务端状态从全局状态中解放出来。
按照来源,前端有两类 状态 需要管理:
在陈年的老项目中,通常用Redux
、Mobx
这样的「全局状态管理方案」无差别对待他们。
事实上,他们有很大区别:
比如组件的isLoading
、isOpen
,这类「状态」的特点是:
isLoading
)这类「状态」通常保存在组件内部。
当「状态」需要跨组件层级传递,通常使用Context API
。
再大范围的「状态」会使用Redux
这样的「全局状态管理方案」。
当我们从服务端请求数据:
function App() {
const [data, updateData] = useState(null);
useEffect(async () => {
const data = await axios.get('/api/user');
updateData(data);
}, [])
// 处理data
}
返回的数据通常作为「状态」保存在组件内部(如App
组件的data
状态)。
如果是需要复用的通用「状态」,通常将其保存在Redux
这样的「全局状态管理方案」中。
这样做有2个坏处:
function App() {
const [data, updateData] = useState(null);
const [isError, setError] = useState(false);
const [isLoading, setLoading] = useState(false);
useEffect(async () => {
setError(false);
setLoading(true);
try {
const data = await axios.get('/api/user');
updateData(data);
} catch(e) {
setError(true);
}
setLoading(false);
}, [])
// 处理data
}
这类通用的中间状态处理逻辑可能在不同组件中重复写很多次。
作为可以由不同组件共享的「缓存」,还需要考虑更多问题,比如:
Redux
一把梭固然方便。但是,区别对待不同类型「状态」能让项目更可控。
这里,推荐使用React-Query
管理服务端状态。
React-Query
是一个基于hooks
的数据请求库。
我们可以将刚才的例子用React-Query
改写:
import { useQuery } from 'react-query'
function App() {
const {data, isLoading, isError} = useQuery('userData', () => axios.get('/api/user'));
if (isLoading) {
return <div>loading</div>;
}
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
)
}
React-Query
中的Query
指一个异步请求的数据源。
例子中userData
字符串就是这个query
独一无二的key
。
可以看到,React-Query
封装了完整的请求中间状态(isLoading
、isError
…)。
不仅如此,React-Query
还为我们做了如下工作:
数据的CRUD
由2个hook
处理:
useQuery
处理数据的查useMutation
处理数据的增/删/改在下面的例子中,点击「创建用户」按钮会发起创建用户的post
请求:
import { useQuery, queryCache } from 'react-query';
unction App() {
const {data, isLoading, isError} = useQuery('userData', () => axios.get('/api/user'));
// 新增用户
const {mutate} = useMutation(data => axios.post('/api/user', data));
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
<button
onClick={() => {
mutate({name: 'kasong', age: 99})
}}
>
创建用户
</button>
</ul>
)
但是点击后userData query
对应数据不会更新,因为他还未失效。
所以我们需要告诉React-Query
,userData query
对应的缓存已经失效,需要更新:
import { useQuery, useQueryClient} from 'react-query';
function App() {
// ...
const queryClient = useQueryClient()
const {mutate} = useMutation(userData => axios.post('/api/user', userData), {
onSuccess: () => {
queryClient .invalidateQueries('userData')
}
})
// ...
}
通过调用mutate
方法,会触发请求。
当请求成功后,会触发onSuccess
回调,回调中调用queryClient .invalidateQueries
,将userData
对应的query
缓存置为invalidate
。
这样,React-Query
就会重新请求userData
对应query
的数据。
1、Hook:
(1)useIsFetching
返回应用程序在后台加载或获取的查询的数量(对于应用程序范围内的加载指示器很有用)。
const isFetching = useIsFetching() 返回所有数目
const isFetching = useIsFetching(key,{配置过滤对象}) 返回key开头的数目
配置过滤对象:
exact:true, 是否开启精确匹配
active:ture, 匹配活跃查询
inactive:true, 匹配不活跃查询
stale:true, 匹配stale过时查询,否则匹配fresh新鲜查询
fetching:true, 匹配正在拉取的查询,否则匹配未正在拉取的查询
predicate: (query) =>布尔值, 匹配返回true的query
queryKey:设置此属性以定义要匹配的查询键
(2)useIsMutating
返回应用程序正在获取的mutation数量
和(1)使用方式相同
2、QueryClient:
查询相关:
(1)queryClient.fetchQuery
异步获取,若缓存中没有或过时了,则会去获取最新结果,相当于同步的queryClient.setQueryData
const data = await queryClient.fetchQuery(queryKey, queryFn,{配置对象})
配置对象大部分和useQuery相同:除了enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, notifyOnChangeProps, notifyOnChangePropsExclusions, onSuccess, onError, onSettled, useErrorBoundary, select, suspense, keepPreviousData, placeholderData
(2)queryClient.fetchInfiniteQuery
无限获取,与fetchQuery类似,但可以用于获取和缓存无限查询。
const data = await queryClient.fetchInfiniteQuery(queryKey, queryFn,{配置对象})
(3)queryClient.prefetchQuery
异步预获取,若缓存中没有或过时了,则会去获取最新结果
const data = await queryClient.fetchQuery(queryKey, queryFn,{配置对象})
配置对象和(1)相同
(4)queryClient.prefetchInfiniteQuery
无限预获取
const data = await queryClient.prefetchInfiniteQuery(queryKey, queryFn,{配置对象})
(5)queryClient.getQueryData
从缓存中获取数据
const data = queryClient.getQueryData(queryKey,{配置过滤对象})
(6)queryClient.setQueryData
设置缓存查询数据,如果在默认的5分钟的cacheTime中查询钩子没有利用该查询,则该查询将被垃圾收集
queryClient.setQueryData(key,值/返回值的方法)
(7)queryClient.getQueryState
const state = queryClient.getQueryState(queryKey,{配置过滤对象})
(8)queryClient.invalidateQueries
使得匹配的查询失效并重新获取
await queryClient.invalidateQueries(key,{
exact:true, 精确匹配key
refetchActive: true, 是否重新获取活跃查询
refetchInactive: true 是否重新获取不活跃查询
},{
throwOnError:true 任何查询重取任务失败都会抛出错误
})
(9)queryClient.refetchQueries
再次获取匹配的查询
await queryClient.refetchQueries()
await queryClient.refetchQueries(key,{配置过滤对象},{
throwOnError:true 任何查询重取任务失败都会抛出错误
})
(10)queryClient.cancelQueries
取消匹配的查询,若是取消axios/fetch等,需要添加额外的cancel属性,详情看之前的文章
await queryClient.cancelQueries(key, {配置过滤对象})
(11)queryClient.removeQueries
从缓存中删除查询
ueryClient.removeQueries(queryKey, {配置过滤对象})
(12)queryClient.resetQueries
将缓存的查询重置为初始状态,如果查询具有initialData,该查询的数据将被重置为该数据,如果查询是活动的会被重新获取
ueryClient.resetQueries(queryKey, {配置过滤对象},{
throwOnError:true 任何查询重取任务失败都会抛出错误
})
(13)queryClient.isFetching
获取缓存中正在background-fetching, loading new pages, or loading more infinite query进行查询的条数
const num=queryClient.isFetching();
const num=queryClient.isFetching(key,{过滤配置对象});
(14)queryClient.isMutating
获取正在提交的mutation的条数
const num=queryClient.isMutating();
const num=queryClient.isMutating({过滤配置对象});
配置相关:
(15)queryClient.getDefaultOptions
获取默认配置,从QueryClient中设置的和setDefaultOptions方法设置的内容
const defaultOptions = queryClient.getDefaultOptions()
(16)queryClient.setDefaultOptions
动态设置QueryClient的默认选项。
queryClient.setDefaultOptions({
queries: {
staleTime: Infinity,
},
})
(17)queryClient.getQueryDefaults
获取指定查询的配置
const defaultOptions = queryClient.getQueryDefaults(key)
(18)queryClient.setQueryDefaults
设置指定查询配置
queryClient.setQueryDefaults(key, { queryFn: fetchPosts })
const { data } = useQuery(key)
(19)queryClient.getMutationDefaults
获取指定mutation的配置
const defaultOptions = queryClient.getMutationDefaults(key)
(20)queryClient.setMutationDefaults
设置指定mutation的配置
queryClient.setMutationDefaults(key, { mutationFn: addPost })
const { data,mutate } = useMutation('addPost')
缓存设置:
(21)queryClient.getQueryCache
获取缓存了的查询
const queryCache = queryClient.getQueryCache()
(22)queryClient.getMutationCache
获取缓存了的mutation
const mutationCache = queryClient.getMutationCache()
(23)queryClient.clear
清除缓存
queryClient.clear()
3、QueryClientProvider:
使用QueryClientProvider组件连接并提供一个QueryClient到你的应用程序:
<QueryClientProvider client={queryClient} contextSharing={false}>
contextSharing:默认为false,若为true,将共享第一个和至少一个实例的上下文窗口,以确保在不同的包或microfrontends他们都使用相同的实例的上下文,不管模块范围。
...
</QueryClientProvider>
4、useQueryClient:
返回当前QueryClient的实例
const queryClient = useQueryClient()
5、QueryCache
存储包含的所有数据、元信息和查询状态,通常是使用queryClient对缓存的操作,而不是直接操作
const queryCache = new QueryCache({
onError: (error,query) => { 某个查询遇到错误触发
console.log(error)
}
})
方法:
.find(key) 用于从缓存获取现有的查询实例,包含查询的所有状态,还包含所有实例和查询的底层内容
.findAll(key,{过滤配置对象}) 返回数组集合
.subscribe((query)=>{...}) 监听订阅缓存中的所有查询,如查询状态更改或查询被更新、添加或删除
const unsubscribe = queryCache.subscribe(callback); 返回一个取消订阅方法
.clear() 完全清除缓存并重新开始
6、MutationCache
存储mutations,通常是使用queryClient对缓存的操作,而不是直接操作
const mutationCache = new MutationCache({
onError: (error,variables, context, mutation) => { 提交错误时触发
console.log(error)
}
})
方法:
.getAll() 返回缓存中所有的mutations
.subscribe.((mutation)=>{ 监听订阅缓存中所有的mutation,如变化的mutation状态或正在更新、添加或删除的mutation
返回一个取消监听订阅的方法
...
})
const unsubscribe = mutationCache.subscribe(callback)
.clear() 完全清除缓存并重新启动。