React Query 是一个开箱即用,零配置的服务端状态管理库,支持Restful和GraphQL两种类型的请求,它能帮助你很好的获取、同步、管理和缓存你的远程数据。它提供了几个简单的Hooks,借助它们可以很轻松的完成对后端数据的增删改查等操作,无需再写繁琐的数据拉取和状态判断等代码;简单来说,`React-query`是一个异步状态管理的hook,内置了loading,error等状态;
官方文档: tanstack.com/query/lates…
Reqct-query的作用
- 封装了loading,error等方便使用,减少代码量
- 多个组件请求同一个query时只发送一个请求;如下所示,List和List1都使用了getListData这个query,但是最终只会请求一次
function List() {
const { isLoading, error, data } = useQuery('getListData', () => fetchList().then(res => res.data));
if (error) {
return 'An error has occurred: ' + error.message
}
if (isLoading) {
return 'data is loading.'
}
return data.map(l => <div key={l.id}>
{l.name}
</div>);
}
function List1() {
const { isLoading, error, data } = useQuery('getListData', () => fetchList().then(res => res.data));
if (error) {
return 'An error has occurred: ' + error.message
}
if (isLoading) {
return 'data is loading.'
}
return data.map(l => <div key={l.id}>
{l.name}
</div>);
}
function App() {
const queryClient = new QueryClient();
return <div className='app'>
<QueryClientProvider client={queryClient}>
<List />
<List1/>
<ReactQueryDevtools initialIsOpen={false} position='top-right'/>
</QueryClientProvider>
</div>
- 缓存更新和时效策略
React-query的一些主要特性:
- 数据获取和缓存:
React-query
可以帮助你从远程服务器或本地存储获取数据,并在本地缓存这些数据。他会自动处理数据的获取、缓存和更新等操作。 - 自动数据刷新:
React-query
可以根据设定的条件自动刷新数据,例如定时刷新或者当页面重新聚焦时刷新等 - 无需手动管理状态:
React-query
会自动管理所有一步操作的状态,并提供一系列钩子函数来让您轻松地与数据交互 - 查询时效和重新获取:当数据过期或失效时,
React-query
会自动触发重新获取机制,确保得到最新的数据 - 错误处理:
React-query
提供了方便的错误处理机制,可以很容易地处理请求失败或其他错误情况 - 多种查询功能:除了基本的查询外,
React-query
还支持带有参数,依赖关系和无限滚动等高级查询功能。
React-query和redux的区别
React-query
和redux
都能进行状态管理和异步数据的处理,React-query
更偏向于服务器之间的异步数据的管理
简单使用
QueryClientProvider
首先,需要在组件外层定义一个queryClient
作为组件操作和使用数据的一个共同容器,通过QueryClientProvider
组件注入到项目中。
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
在创建QueryClient
的时候,我们可以传入一些参数,用于管理项目中的请求,缓存,日志的相关配置,这些配置会对整个项目生效,其中包含了四个模块的配置参数
new QueryClient({
queryCache?: QueryCache;//请求缓存相关配置
mutationCache?: MutationCache;//数据修改缓存相关配置
logger?: Logger;//日志相关配置
defaultOptions?: DefaultOptions;//请求基础配置
})
其中,defaultOptions
用于配置项目中useQuery
请求的管理,常用的配置如下:
- staleTiem:重新获取数据的时间间隔,默认为0
- cacheTime:设置数据在缓存中保留的时间,超过此时间后数据将被自动清除,默认值为5分钟
- retry:失败重试次数,默认3次;
- refetchOnWindowFocus:窗口重新获得焦点时重新获取数据,默认为false
- refetechOnReconnect:网络重新连接
- refetchOnMount:实例重新挂载时重新拉取请求
- enabled:如果为false,
useQuery
不会触发,需要使用其返回的refetch来触发操作 - queryFn:全局定义请求方法,其他地方使用时只需要直接传入请求参数
useQuery
useQuery
是React-query
提供的用于请求接口并管理请求状态等信息的Hook
export const useGetAttachments = (subjectId: string) => {
const { data, ...rest } = useQuery({
queryKey: ['querySubjectAttachments',subjectId],
queryFn: () => {
return queryAllAttachments(subjectId).then((data: ResBasic<IAttachmentRes>) => {
return data;
});
},
});
return {
dataSourceList: (data?.data?.files ?? []).map((item: IAttachmentItem) => {
return {
title: item.file_name,
size: item.file_size,
fileType: item.type,
fileId: item.file_uuid,
downloadUrl: item.download_url,
};
}),
...rest,
};
};
useQuery
接收一个配置对象,其中:
queryKey
:必传,用于请求数据缓存唯一的key值,也可以在数组中,写入多项如:['querySubjectAttachments', subjectId]这样React-Query
在使用的时候会自动把它拼接为/querySubjectAttachments/subjectId
,这个在缓存用户访问过的页面时,非常有用。- 参数较多的情况,可以在queryKey键值里面分别展开,如下所示:
export const useGetPatientList = (params: IQuerySubejctPatientList) => {
const { data, ...rest } = useQuery({
queryKey: ['querySubjectPatientList', params.subjectId, params.patient_filter],
queryFn: () => {
return querySubejctPatientList(params).then((data: ResBasic<IPatientListRowsRes>) => {
return data;
});
},
});
return {
viewRows: [...processData(data)],
totalCounts: data?.data.rows?.length ? data?.data.page_info.total_rows : 0,
...rest,
};
};
const processData = (data: ResBasic<IPatientListRowsRes> | undefined) => {
return (data?.data?.rows ?? []).map((row: IPatientListRows) => {
const map = new Map();
row.records.forEach((record) => {
map.set(record.field_uuid, record.value);
});
map.set(RowKey, row.row_uuid);
map.set(PatientKey, row.patient_uuid);
map.set(IsFavoriteKey, row.is_favorite);
return Object.fromEntries(map.entries());
});
};
- queryFn:用于请求的方法,如果在
QueryClient
中配置了,这里可以不必再写,需要返回请求完成后所处理的数据; - 除了上述两项基本的参数,
useQuery
还可以传入上面defaultOptions的所有参数,来表示对这个单独的配置。 useQuery
会返回一个对象,里面包含着请求相关的所有信息,这些信息会随着请求的进度而改变,就无需我们再使用一组state变量来进行管理了,常用的包括:- isLoading:请求是否正在进行;
- error:请求是否出错,这里只会对500,404等做出反应,如果有其他情况,需要再请求方法里面throw
- isSuccess:请求是否成功
- status:请求状态,包含上面几种情况
- data:返回数据 除此之外,useQuery拉取回来的数据,会被默认缓存起来,然后可以通过配置过期时间,重新拉取等
useQueryClient
通过useQueryClient
,我们可以获取到之前注入的容器实例,里面保存着所有我们缓存的信息,以及配置信息,而它本质上其实也是对React.useContext
的封装。
function Example() {
const queryClient = useQueryClient()
const data = queryClient.getQueryData(['querySubjectAttachments'])
return (
//...
)
}
除了访问缓存数据,queryClient还有很多API:
- queryClient.queryKeyCache: 这个属性包含了所有当前已经缓存的查询键和相应的查询结果;
- queryClient.fetchQuery: 是一个异步方法,可用于获取和缓存查询。它要么用数据解析,要么用错误抛出。如果你只想获取一个不需要结果的查询,可以使用
prefetchQuery
方法。如果查询存在,且数据不是无效的,也不是比给定staleTime
早,那么将返回来自缓存的数据。否则,它将尝试获取最新的数据。
try {
const data = await queryClient.fetchQuery({ queryKey, queryFn })
} catch (error) {
console.log(error)
}
// 指定一个staleTime,当缓存过期的时候才会去获取数据
try {
const data = await queryClient.fetchQuery({
queryKey,
queryFn,
staleTime: 10000,
})
} catch (error) {
console.log(error)
}
//fetchQuery的options和useQuery的配置一样,除去这些配置: enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, refetchOnMount, notifyOnChangeProps, throwOnError, select, suspense, placeholderData
- queryClient.fetchInfiniteQuery: 该方法与
fetchQuery
一样,但可用于获取缓存无限查询; - queryClient.prefetchQuery({queryKey,queryFn,options?}):是一种异步方法,可以在需要查询之前预获取查询;该方法的工作原理和fetchQuery相同,只是它不抛出和返回任何数据。其中,配置和fetchQuery一样。
- queryClient.prefetchInfiniteQuery:该方法与
prefetchQuery
一样,但可用于获取缓存无限查询; - queryClient.getQueryData(queryKey):是一个同步函数,根据指定的查询键从缓存中获取数据;如果查询不存在,则返回
undefined
; - queryClient.ensureQueryData({queryKey,queryFn}): 是一个异步函数,可用于获取对应键的缓存数据,如果查询不存在,那将会触发
queryClient.fetchQuery
方法并返回结果; - queryClient.setQueryData(queryKey,data,options?): 将指定数据存储到缓存中,并触发相应更新;
- queryClient.invalidateQueries(queryKey,options?): 使特定查询失效,下次访问时会重新获取最新数据;
- queryClient.invalidateQueries((key)=>key.startsWith('examplae)): 通过函数形式批量使多个查询失效
- queryClient.refetchQueries(queryKeyOrPredicateFn,options?): 手动触发重新获取指定或匹配条件的查询数据。
- queryClient.cancelQueries(queryKey,options?):取消正在进行中的查询请求,避免不必要的网络请求
- queryClient.removeQueries
- queryClient.resetQueries
- queryClient.clear():清除整个queryClient内容,包括所有的queries和mutations数据 …
useMutation
除了获取数据,很多时候还需要处理数据的修改,比如说最简单的todolist例子,除了拉取数据列表,还需要增删改数据,而这个时候除了需要发送接口,还需要修改本地的数据,React-Query
提供了useMutation
来帮我们完成这些事情。
用法:
const {
data,
error,
isError,
isIdle,
isLoading,
isPaused,
isSuccess,
failureCount,
failureReason,
mutate,
mutateAsync,
reset,
status,
} = useMutation({
mutationFn,
cacheTime,
mutationKey,
networkMode,
onError,
onMutate,
onSettled,
onSuccess,
retry,
retryDelay,
useErrorBoundary,
meta,
})
mutate(variables, {
onError,
onSettled,
onSuccess,
})
比如,我们已经拉取了data,现在我们想新增一条数据,那我们可以:
const {isLoading,isError,isSuccess,mutate} = useMutation({
mutationFn: async (newData) => insertNewData(newData),
onSuccess: (data: TData,variables: TVariables,context?: TContext) => {
queryClient.invalidateQueries({ queryKey: ['repoData'] });
},
onError: (error) => {
console.log(error)
}
});
其中:
- mutationFn:必传;代表元数据的方法
- onSuccess:可选;成功的回调函数;接口调用成功后的回调,其可接受三个参数,分别为:
- 第一个参数data:为接口成功后返回的值;
- 第二个参数variables:是发起这个请求时携带的参数,因此在成功回调函数里不仅可以获得后端返回的结果,还可以访问到发起请求携带的参数;
- 第三个参数context:表示上下文数据,它是一个可选参数,通常用于在进行mutation请求时传递一些额外的上下文信息或数据【比如用户登录状态,认证信息等】。这样可以在成功回调函数中访问并使用这些上下文数据。
- onError:可选;失败的回调;
返回的数据和
useQuery
基本相同,这里的mutate
则是触发更改的方法,如果我们想执行useMutation
中传入的方法,我们只需要调用mutate
即可,传给mutate的参数都会被传到useMutation
的构造方法中。
const updateDate = async (newData)=>{
mutate(newData);
}
//传入上下文信息
const updateDate = async (newData)=>{
const contextData = { auth: true }; // 定义上下文数据
mutate(newData,
{context: contextData});// 传递上下文数据
}
其他详见:tanstack.com/query/v4/do…
React-Query
除了以上的最核心的对服务端数据进行增删改查的功能,除此之外,React-Query
还有很多其他的能力。
useQueries
useQueries
是一个用于同时发起多个查询的React Hook
。它允许在一个组件中同时发起多个查询,并返回一个包含多个查询结果的数组,这个Hook对于需要一次性获取多个数据的情况非常有用。其特点和主要作用有:
- 并行查询:useQueries允许在同一时间点发起多个并行查询,而不需要按顺序等待每一个查询完成后再进行下一个查询。
- 单独控制每个查询:每个查询都可以单独控制,包括设置缓存策略,触发重新获取,出错处理等。
- 返回值:
useQueries
返回一个数组,数组中包含了所有并行查询的结果。可以通过遍历这个数组来访问每一个查询的结果。 - 处理loading状态:可以方便地处理并显示组件加载状态,例如显示加载指示器直到所有并行查询完成。
- 使用方式:通常使用
useQueries
结合queryKey
和queryFn
来定义多个同时执行的异步操作。如下所示:
const queries = useQueries([
{queryKey:'key1',queryFn:fetchData},
{queryKey:'key2',queryFn:fetchData},
{queryKey:'key3',queryFn:fetchData},
])
//处理每个查询结果
queries.forEach((query)=>{
if(query.loading){
return <div>Loading....</div>;
}
if(query.error){
return <div>Error: {query.error.message}</div>;
}
return <div>data:{query.data}</div>
})
如果需要传参,可以这样写:
// 并行获取初始数据
export const useQuerysViewInitalData = (params: {
queryViewBaseInfoParams: QueryViewBaseInfoParams;
queryViewFilterConfigParams: QueryViewFilterConfigParams;
queryViewFilterConditionParams: QueryViewFilterConditionParams;
}) => {
return useQueries({
queries: [
{
queryKey: QUERYKEY.viewBaseInfo(params.queryViewBaseInfoParams),
queryFn: () => queryViewBaseInfo(params.queryViewBaseInfoParams),
staleTime: 0,
},
{
queryKey: QUERYKEY.viewFilterConfig(params.queryViewFilterConfigParams),
queryFn: () => queryViewFilterConfig(params.queryViewFilterConfigParams),
staleTime: 0,
},
{
queryKey: QUERYKEY.supportOpertor(),
queryFn: () => querySupportOperator(),
staleTime: 0,
},
],
});
};
数据预获取
有时候我们不需要整个页面loading来等待数据加载,我们更希望在用户操作之前就完成数据拉取,比如hover详情链接,而不是点击详情的时候;则可以使用queryClient的prefetchQuery
方法,提前拉取到用户可能会访问的数据,并加入缓存中,由于不需要监听服务端状态,所以这个方法回避useQuery高效许多。
onMouseEnter={async ()=>{
await queryClient.prefetchQuery({
queryKey: ['character',char.id],
queryFn:()=>getCharacter(char.id),
staleTime: 10*1000,//只有超过10s之后才开始重新获取
})
}}
分页缓存
通过动态设置queryKey,并将keepPreviousDate
设置为true,我们可以很轻松的缓存之前页码的数据
const {status, data,error,isFetching,isPreviousData} = useQuery({
queryKey:['projects',page],
queryFn:()=>fetchProjects(page),
keepPreviousData:true,
staleTime: 5000
})
滚动列表渲染
使用useInfiniteQuery
定义拉取获取数据的方法,以及上下页的逻辑,然后返回更新页面数据的状态,以及触发更新的方法;
const {status,data,error,isFetchingNextPage,isFetchingPreviousPage,fetchNextPage,fetchPreviousPage,hasNextPage,hasPreviousPage} useInfiniteQuery(['projects'],async ({pageParam = 0})=>{
const res = await axios.get('/api/projects?cursor='+pageParam)
return res.data
},{
getPreviousPageParam: ( firstPage ) => firstPage.previousId ?? undefined,
getNextPageParam: ( lastPage ) => lastPage.nextId ?? undefined
})
devTools配套开发工具
导入开发工具
import {ReactQueryDevtools} from 'react-query/devtools'
<ReactQueryDevtools initialOpen={false}>
默认情况下,当process.env.NODE_ENV==='production
时开启Devtools,不必担心构建时需要排除他们;
浮动模式下开启,会将devtools作为固定的浮动元素安装在开发的应用程序中,并在屏幕一角提供一个切换按钮以显示和隐藏devtools,在devtools中我们可以直观的看到已经缓存下来的数据和整个项目的配置,以及各个接口的状态等