@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,在每次挂载组件时自动触发重新获取。
image.png
queryClient.getQueryData就不会在拖线的时候发送请求了

  const queryClient = useQueryClient();
  const flowDetail = queryClient.getQueryData<IFlow>(['flowDetail']);

4. 在不同组件共享useMutation获取的数据

通常都是用useQuery去获取代get方法的接口的数据的,但是有时候后端给的接口是post,需要提交表单数据,这个时候需要用button触发接口的调用,如果用useQuery的话,需要使用enabled:false禁用useQuery的默认加载调用的行为,然后结合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;
};

5. 模糊匹配useQuery缓存的数据

列表页面往往有很多查询条件,比如分页,搜索,排序等,@tanstack/react-query@5.35.5推荐将查询条件写进queryKey作为依赖,从而触发接口的重新请求,但是我们在不同的组件希望拿到被@tanstack/react-query@5.35.5缓存的数组,而不是层层传递,useQuery代码如下,
image.png
如果在不同的组件里使用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也不行。有待讨论

f20d206dd4bb04a1bdee6861d0aca6b.png

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


assassin_cike
1.3k 声望74 粉丝

生活不是得过且过


« 上一篇
react 继续踩坑
下一篇 »
SSE post 实践