React Query:使用具有不同设置的多个查询客户端?

ReactQuery: use multiple QueryClients with different settings?

我有一个 React SPA,我正在尝试使用 ReactQuery 库。我从我的服务器 API 获取了很多不同的数据——有些比较不稳定,有些则相对稳定。我想将稳定的数据存储在浏览器的本地存储中,以减少对服务器的请求数量(从而使 UI 应用程序更快一些)并每 24 小时刷新一次。但是,我想在每次请求时使易失性数据无效并重新获取,因为其他用户可能同时更改了数据。

目前,我的应用程序只有一个简单的 ReactQueryProvider:

import React from 'react';
import { QueryClientProvider } from 'react-query';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import { CookiesProvider } from 'react-cookie';
import { QueryClient } from 'react-query';
import { HttpError } from 'types';

const MaxFailureCount = 2;
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href') || undefined;
const rootElement = document.getElementById('root');

function shouldRetry(failureCount: number, error: unknown): boolean {
  if (error instanceof HttpError) {
    if (!error.isRetryableError()) {
      return false;
    }
  }

  return failureCount < MaxFailureCount;
}
const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        refetchOnMount: false,
        refetchOnWindowFocus: false,
        refetchOnReconnect: false,
        retry: shouldRetry,
      },
    },
  });

ReactDOM.render(
  <QueryClientProvider client={queryClient}>
    <BrowserRouter basename={baseUrl}>
      <CookiesProvider>
        <App />
      </CookiesProvider>
    </BrowserRouter>
  </QueryClientProvider>,
  rootElement
);

如果我想使用 persistQueryClient 插件,我必须将 cacheTime 添加到我的 queryClient,然后将客户端传递给 localStoragePersistor。如果我正确理解了文档,这将改变我所有的 useQuery 调用以将数据存储在本地存储中 24 小时 - 即使是更不稳定的数据。

可以选择将 buster 字符串传递给持久化缓存,但这会使所有数据(甚至稳定数据)失效。有没有办法使用两个不同的queryClients?或者,在每个 useQuery 调用中指定是否应缓存响应?或者我应该手动将稳定数据 API 的响应放入本地存储?如果是这样,我该如何处理失效?

QueryClient 只是一个包含 queryCache、mutationCache 和默认设置的容器。如果您想创建一个新的 QueryClient,并复制缓存,您可以设置新的默认设置并仍然保留缓存。

一个基本的实现是:

const ReactQueryConfigProvider = ({ children, defaultOptions }) => {
    const client = useQueryClient()
    const [newClient] = React.useState(
        () =>
            new QueryClient({
                queryCache: client.getQueryCache(),
                muationCache: client.getMutationCache(),
                defaultOptions,
            })
    )
    return <QueryClientProvider client={newClient}>{children}</QueryClientProvider>
}

这是一个codesandbox where this is used in action (I've taken this from my blog: https://tkdodo.eu/blog/testing-react-query)。不确定这是否会帮助您解决 persistQueryClient 问题,但如果您只想更改 componentTree 某些部分的 cacheTime,这就可以了。

最终,我决定创建自己的本地存储挂钩。这很简单,我可以重新使用默认的 QueryClient 而无需任何额外的配置:

import { HttpError } from 'types';
import { useQuery, UseQueryOptions } from 'react-query';
import { get } from 'utils/request';

const defaultStaleTime = 24 * 60 * 60 * 1000; // 24 hours

type CachedQueryProps<T> = {
  path: string;
  staleTime?: number | null | undefined;
  forceRefresh?: boolean | null | undefined;
  options?: UseQueryOptions<T, HttpError, T> | undefined;
};

type CachedQueryResult<T> = {
  data: T | undefined;
  isLoading: boolean;
  error: HttpError | null;
};

type CachedObject<T> = {
  data: T;
  refreshTime: number;
};

export function useCachedQuery<T>(props: CachedQueryProps<T>): CachedQueryResult<T> {
  const now = new Date().getTime();
  const lowestAllowedTime = now - (props.staleTime ?? defaultStaleTime);
  const item = window.localStorage.getItem(props.path);
  const result = (item && (JSON.parse(item) as CachedObject<T> | null)) || null;
  const refresh = !result || result.refreshTime < lowestAllowedTime || !!props.forceRefresh;
  const query = useQuery<T, HttpError>(props.path, () => get<T>(props.path), { ...props.options, enabled: refresh });

  if (query.isSuccess) {
    const newData = { data: query.data, refreshTime: now };
    localStorage.setItem(props.path, JSON.stringify(newData));
    return query;
  }

  return { data: result?.data, isLoading: false, error: null };
}