@tanstack/react-query@5.35.5
1. isPending isLoading isFetching 傻傻分不清
const { data: knowledgeList, isFetching: loading } = useQuery({
queryKey: ['knowledgeList'],
initialData: [],
gcTime: 0,
});
useQuery的isFetching是接口在请求中
React Query 5: When to use isLoading, isFetching, and isRefetching
2. 单独获取一个接口的loading状态
useQuery()会返回isFetching,但是往往component是分开写的,就是发起请求在一个component,而Spin在另一个component,这时候就需要独立的拿到isFetching
import {
useIsFetching,
} from '@tanstack/react-query';
export const useKnowledgeDetailIsFetching = () => {
return useIsFetching({ queryKey: ['knowledgeDetail'] }) > 0;
};
注意:useIsFetching()返回的是数字
Background Fetching Indicators
同理,对于useMutation
export const useChunkIsTesting = () => {
return useIsMutating({ mutationKey: ['testChunk'] }) > 0;
};
3. queryClient.getQueryData与useQuery获取共享数据的区别
比如页面加载的时候使用useQuery
请求到了数据,被@tanstack/react-query缓存了起来,在其他组件里想拿到该数据,通常会直接调用useQuery获取数据,但是在项目里出了问题,如下图,我在两个节点拖拽无法建立连线,因为线跟后端返回的数据是管理的,边节点里面调用了useQuery
,每次有新线连接就会调用useQuery
,这样导致我客户端的数据被接口返回的数据所覆盖,从而连接不成功。根本原因在于retryOnMount
参数为true
,在每次挂载组件时自动触发重新获取。
换queryClient.getQueryData
就不会在拖线的时候发送请求了
const queryClient = useQueryClient();
const flowDetail = queryClient.getQueryData<IFlow>(['flowDetail']);
4. 在不同组件共享useMutation获取的数据
通常都是用useQuery
去获取代get方法的接口的数据的,但是有时候后端给的接口是post,需要提交表单数据,这个时候需要用button触发接口的调用,如果用useQuery
的话,需要使用enabled:false
禁用useQuer
y的默认加载调用的行为,然后结合refetch
函数去手动调用,但是refetch
不能传递参数,需要将参数传到state或者redux、zustand等状态管理库托管,所以还是用useMutation
方便点,但是怎么在不同组件共享useMutation
获取的数据?
export const useSelectTestingResult = (): ITestingResult => {
const data = useMutationState({
filters: { mutationKey: ['testChunk'] },
select: (mutation) => {
return mutation.state.data;
},
});
return (data.at(-1) ?? { // 获取接口返回的最新的一条数据
chunks: [],
documents: [],
total: 0,
}) as ITestingResult;
};
参考:
Why useMutation is designed as not able to share status and data between multiple component? #6118
5. 模糊匹配useQuery缓存的数据
列表页面往往有很多查询条件,比如分页,搜索,排序等,@tanstack/react-query@5.35.5推荐将查询条件写进queryKey作为依赖,从而触发接口的重新请求,但是我们在不同的组件希望拿到被@tanstack/react-query@5.35.5缓存的数组,而不是层层传递,useQuery代码如下,
如果在不同的组件里使用useFetchNextChunkList,如果有组件mount,则useFetchNextChunkList会被多次执行,会导致每次的查询条件都是初始值,因为useState会被重新执行,所以只好选择 getQueriesData
, Share state between components #2310 这种方式行不通
export const useSelectChunkList = () => {
const queryClient = useQueryClient();
const data = queryClient.getQueriesData<{
data: IChunk[];
total: number;
documentInfo: IKnowledgeFile;
}>({ queryKey: ['fetchChunkList'] });
return data[0][1];
};
6. 缓存全局数据
Using react-query to store global state? #2852
7. 在发出请求到拿到数据中间会返回初始值initialData
我想保留之前的查询结果,会导致组件无效的rerender,即使用了placeholderData: keepPreviousData
也不行。有待讨论
8. 自定义staleTime: 20 * 1000
,设置initialData跟不设置该值有不同的表现
阅读了 React Query as a State Manager 自己做了如下测试
demo.tsx
import { useFetchFlowTemplates } from '@/hooks/flow-hooks';
const Inner = () => {
const ret = useFetchFlowTemplates();
const data = ret?.data;
return <ul>{data?.map((x) => <li key={x.id}>{x.title}</li>)}</ul>;
};
const Demo = () => {
const ret = useFetchFlowTemplates();
const data = ret?.data;
return (
<section>
<h6>{data?.length}</h6>
{data && <Inner></Inner>}
</section>
);
};
export default Demo;
hooks
export const useFetchFlowTemplates = (): ResponseType<IFlowTemplate[]> => {
const { data } = useQuery({
queryKey: ['fetchFlowTemplates'],
staleTime: 20 * 1000,
initialData: [],
queryFn: async () => {
const { data } = await flowService.listTemplates();
return data;
},
});
return data;
};
上述代码不会发送请求,将initialData注释掉,如下,可以正常发送一条请求,有待进一步探究
export const useFetchFlowTemplates = (): ResponseType<IFlowTemplate[]> => {
const { data } = useQuery({
queryKey: ['fetchFlowTemplates'],
staleTime: 20 * 1000,
// initialData: [],
queryFn: async () => {
const { data } = await flowService.listTemplates();
return data;
},
});
return data;
};
9. useQuery分页查询,会重置之前请求到的数据
上代码:
export const useFetchNextChunkList = (): ResponseGetType<{
data: IChunk[];
total: number;
documentInfo: IKnowledgeFile;
}> &
IChunkListResult => {
const { pagination, setPagination } = useGetPaginationWithRouter();
const { documentId } = useGetKnowledgeSearchParams();
const { searchString, handleInputChange } = useHandleSearchChange();
const [available, setAvailable] = useState<number | undefined>();
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
const { data, isFetching: loading } = useQuery({
queryKey: [
'fetchChunkList',
documentId,
pagination.current,
pagination.pageSize,
debouncedSearchString,
available,
],
initialData: { data: [], total: 0, documentInfo: {} },
// placeholderData: keepPreviousData,
gcTime: 0,
queryFn: async () => {
const { data } = await kbService.chunk_list({
doc_id: documentId,
page: pagination.current,
size: pagination.pageSize,
available_int: available,
keywords: searchString,
});
if (data.code === 0) {
const res = data.data;
return {
data: res.chunks,
total: res.total,
documentInfo: res.doc,
};
}
return (
data?.data ?? {
data: [],
total: 0,
documentInfo: {},
}
);
},
});
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
setPagination({ page: 1 });
handleInputChange(e);
},
[handleInputChange, setPagination],
);
const handleSetAvailable = useCallback(
(a: number | undefined) => {
setPagination({ page: 1 });
setAvailable(a);
},
[setAvailable, setPagination],
);
return {
data,
loading,
pagination,
setPagination,
searchString,
handleInputChange: onInputChange,
available,
handleSetAvailable,
};
};
每次在切换页码的时候useQuery之前返回的数据都会被重置为initialData,从而导致依赖该数据的页面其他部分在切换页面的时候被重新渲染。但是按照官网的示例做加上参数 placeholderData: keepPreviousData,
并不能完全解决这个问题Paginated / Lagged Queries
解决办法:
删除initialData
,加上
placeholderData: (previousData) => previousData ?? []
Conflict Between initialData and placeholderData – Either undefined or UI Flicker #8183
10. 关于 gcTime
需求:编辑跟新增为同一个modal,编辑的时候调用接口并显示数据,新增的时候不调用接口切且不显示数据,如下图所示
代码如下,
ChunkCreatingModal.tsx
const { data } = useFetchChunk(chunkId);
useEffect(() => { // modal显示,从接口获取数据,将数据赋值给form
if (data?.code === 0) {
const { content_with_weight, available_int } = data.data;
form.setFieldsValue({ ...data.data, content: content_with_weight });
}
}, [data, form, chunkId]);
hooks.ts
export const useFetchChunk = (chunkId?: string): ResponseType<any> => {
const { data } = useQuery({
queryKey: ['fetchChunk'],
enabled: !!chunkId, // 只有编辑的时候才调用接口函数
initialData: {},
queryFn: async () => {
const data = await kbService.get_chunk({
chunk_id: chunkId,
});
return data;
},
});
return data;
};
上面的代码只能保证新增的时候,不调用接口,但是点击新增按钮,弹窗依然会显示之前的数据,那怎么办?
Query results that have no more active instances of useQuery, useInfiniteQuery or query observers are labeled as "inactive" and remain in the cache in case they are used again at a later time.
By default, "inactive" queries are garbage collected after 5 minutes.
To change this, you can alter the default gcTime for queries to something other than 1000 * 60 * 5
milliseconds.
翻译过来是:
如果查询结果没有更多的 useQuery、 useInfiniteQuery 或查询观察者的活动实例,那么它们将被标记为 “非活动”,并保留在缓存中,以防日后再次使用。
默认情况下,“非活动” 查询在 5 分钟后被垃圾收集。
要改变这一点,您可以将查询的默认 gcTime 更改为 1000 * 60 * 5
毫秒以外的值。
结合tanstack query 开发者工具也可以看出:
当useQuery返回的数据被引用的时候,该query会被标位stale状态,如果我把modal关了,再看下该query的状态
这个时候query被标记为inactive状态,经过五分钟后,该query会从开发工具中消失,也就是被垃圾回收了。所以如果需要实现开头的功能只需要将gcTime设置为0,modal关闭,则该query失去了引用,那么query返回的数据就会被垃圾回收机制直接回收。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。