框架
版本

persistQueryClient

这是一组用于与“持久化器”交互的实用工具,这些持久化器会保存您的 queryClient 以供将来使用。不同的持久化器可用于将您的 client 和缓存存储到许多不同的存储层。

构建持久化器

工作原理

重要提示 - 要使持久化正常工作,您可能需要为 QueryClient 传递一个 gcTime 值,以覆盖水合(hydration)期间的默认值(如上所示)。

如果在创建 QueryClient 实例时未设置,它将默认为水合的 300000(5 分钟),并且存储的缓存将在不活动 5 分钟后被丢弃。这是默认的垃圾回收行为。

它应该被设置为与 persistQueryClient 的 maxAge 选项相同或更高的值。例如,如果 maxAge 是 24 小时(默认值),则 gcTime 应为 24 小时或更高。如果小于 maxAge,垃圾回收将生效,并比预期提前丢弃存储的缓存。

您也可以将其设置为 Infinity 来完全禁用垃圾回收行为。

由于 JavaScript 的限制,允许的最大 gcTime 约为 24 天,但可以使用 timeoutManager.setTimeoutProvider 来规避此限制。

tsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24, // 24 hours
    },
  },
})
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24, // 24 hours
    },
  },
})

缓存失效

有时,您可能对应用程序或数据进行了更改,这些更改会立即使所有缓存数据失效。如果发生这种情况,您可以传递一个 buster 字符串选项。如果找到的缓存不包含此 buster 字符串,它将被丢弃。以下几个函数接受此选项

tsx
persistQueryClient({ queryClient, persister, buster: buildHash })
persistQueryClientSave({ queryClient, persister, buster: buildHash })
persistQueryClientRestore({ queryClient, persister, buster: buildHash })
persistQueryClient({ queryClient, persister, buster: buildHash })
persistQueryClientSave({ queryClient, persister, buster: buildHash })
persistQueryClientRestore({ queryClient, persister, buster: buildHash })

移除

如果找到的数据是以下任何一种

  1. 已过期(参见 maxAge
  2. 已失效(参见 buster
  3. 错误(例如:throws ...
  4. 为空(例如:undefined

将调用持久化器的 removeClient() 函数,缓存将立即被丢弃。

API

persistQueryClientSave

  • 您的查询/突变被 脱水(dehydrated) 并由您提供的持久化器保存。
  • createSyncStoragePersistercreateAsyncStoragePersister 对此操作进行节流,每秒最多发生一次,以节省可能昂贵的写入操作。请查阅它们的文档,了解如何自定义节流时序。

您可以使用此功能在您选择的时刻显式持久化缓存。

tsx
persistQueryClientSave({
  queryClient,
  persister,
  buster = '',
  dehydrateOptions = undefined,
})
persistQueryClientSave({
  queryClient,
  persister,
  buster = '',
  dehydrateOptions = undefined,
})

persistQueryClientSubscribe

每当您的 queryClient 的缓存发生更改时,就会运行 persistQueryClientSave。例如:当用户登录并勾选“记住我”时,您可以发起 subscribe

  • 它返回一个 unsubscribe 函数,您可以使用它来停止监控;结束对持久化缓存的更新。
  • 如果您想在调用 unsubscribe 后删除持久化缓存,您可以向 persistQueryClientRestore 发送一个新的 buster,这将触发持久化器的 removeClient 函数并丢弃持久化缓存。
tsx
persistQueryClientSubscribe({
  queryClient,
  persister,
  buster = '',
  dehydrateOptions = undefined,
})
persistQueryClientSubscribe({
  queryClient,
  persister,
  buster = '',
  dehydrateOptions = undefined,
})

persistQueryClientRestore

  • 尝试从持久化器 水合(hydrate) 一个先前持久化的脱水查询/突变缓存,并将其恢复到传入的 query client 的查询缓存中。
  • 如果找到的缓存比 maxAge(默认 24 小时)还要旧,它将被丢弃。此计时可以根据需要进行自定义。

您可以使用此功能在您选择的时刻恢复缓存。

tsx
persistQueryClientRestore({
  queryClient,
  persister,
  maxAge = 1000 * 60 * 60 * 24, // 24 hours
  buster = '',
  hydrateOptions = undefined,
})
persistQueryClientRestore({
  queryClient,
  persister,
  maxAge = 1000 * 60 * 60 * 24, // 24 hours
  buster = '',
  hydrateOptions = undefined,
})

persistQueryClient

执行以下操作

  1. 立即恢复任何持久化缓存(参见 persistQueryClientRestore
  2. 订阅查询缓存并返回 unsubscribe 函数(参见 persistQueryClientSubscribe)。

此功能从 3.x 版本保留。

tsx
persistQueryClient({
  queryClient,
  persister,
  maxAge = 1000 * 60 * 60 * 24, // 24 hours
  buster = '',
  hydrateOptions = undefined,
  dehydrateOptions = undefined,
})
persistQueryClient({
  queryClient,
  persister,
  maxAge = 1000 * 60 * 60 * 24, // 24 hours
  buster = '',
  hydrateOptions = undefined,
  dehydrateOptions = undefined,
})

选项

所有可用选项如下

tsx
interface PersistQueryClientOptions {
  /** The QueryClient to persist */
  queryClient: QueryClient
  /** The Persister interface for storing and restoring the cache
   * to/from a persisted location */
  persister: Persister
  /** The max-allowed age of the cache in milliseconds.
   * If a persisted cache is found that is older than this
   * time, it will be **silently** discarded
   * (defaults to 24 hours) */
  maxAge?: number
  /** A unique string that can be used to forcefully
   * invalidate existing caches if they do not share the same buster string */
  buster?: string
  /** The options passed to the hydrate function
   * Not used on `persistQueryClientSave` or `persistQueryClientSubscribe` */
  hydrateOptions?: HydrateOptions
  /** The options passed to the dehydrate function
   * Not used on `persistQueryClientRestore` */
  dehydrateOptions?: DehydrateOptions
}
interface PersistQueryClientOptions {
  /** The QueryClient to persist */
  queryClient: QueryClient
  /** The Persister interface for storing and restoring the cache
   * to/from a persisted location */
  persister: Persister
  /** The max-allowed age of the cache in milliseconds.
   * If a persisted cache is found that is older than this
   * time, it will be **silently** discarded
   * (defaults to 24 hours) */
  maxAge?: number
  /** A unique string that can be used to forcefully
   * invalidate existing caches if they do not share the same buster string */
  buster?: string
  /** The options passed to the hydrate function
   * Not used on `persistQueryClientSave` or `persistQueryClientSubscribe` */
  hydrateOptions?: HydrateOptions
  /** The options passed to the dehydrate function
   * Not used on `persistQueryClientRestore` */
  dehydrateOptions?: DehydrateOptions
}

实际上有三个接口可用

  • PersistedQueryClientSaveOptions 用于 persistQueryClientSavepersistQueryClientSubscribe(不使用 hydrateOptions)。
  • PersistedQueryClientRestoreOptions 用于 persistQueryClientRestore(不使用 dehydrateOptions)。
  • PersistQueryClientOptions 用于 persistQueryClient

在 React 中使用

persistQueryClient 将尝试恢复缓存并自动订阅后续更改,从而将您的 client 同步到提供的存储。

但是,恢复是异步的,因为所有持久化器本质上都是异步的,这意味着如果您在恢复过程中渲染您的 App,可能会在查询挂载和获取的同时发生竞态条件。

此外,如果您在 React 组件生命周期之外订阅更改,您将无法取消订阅。

tsx
// 🚨 never unsubscribes from syncing
persistQueryClient({
  queryClient,
  persister: localStoragePersister,
})

// 🚨 happens at the same time as restoring
ReactDOM.createRoot(rootElement).render(<App />)
// 🚨 never unsubscribes from syncing
persistQueryClient({
  queryClient,
  persister: localStoragePersister,
})

// 🚨 happens at the same time as restoring
ReactDOM.createRoot(rootElement).render(<App />)

PersistQueryClientProvider

对于这种用例,您可以使用 PersistQueryClientProvider。它将确保根据 React 组件生命周期正确订阅/取消订阅,并确保在恢复过程中查询不会开始获取。查询仍然会渲染,只是它们将被置于 fetchingState: 'idle' 状态,直到数据恢复。然后,它们将重新获取,除非恢复的数据足够新鲜,并且initialData也将得到尊重。它可以代替普通的 QueryClientProvider 使用。

tsx
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24, // 24 hours
    },
  },
})

const persister = createAsyncStoragePersister({
  storage: window.localStorage,
})

ReactDOM.createRoot(rootElement).render(
  <PersistQueryClientProvider
    client={queryClient}
    persistOptions={{ persister }}
  >
    <App />
  </PersistQueryClientProvider>,
)
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24, // 24 hours
    },
  },
})

const persister = createAsyncStoragePersister({
  storage: window.localStorage,
})

ReactDOM.createRoot(rootElement).render(
  <PersistQueryClientProvider
    client={queryClient}
    persistOptions={{ persister }}
  >
    <App />
  </PersistQueryClientProvider>,
)

Props

PersistQueryClientProvider 接受与 QueryClientProvider 相同的 props,并另外接受:

  • persistOptions: PersistQueryClientOptions
  • onSuccess?: () => Promise<unknown> | unknown
    • 可选
    • 在初始恢复完成后将被调用
    • 可用于 resumePausedMutations
    • 如果返回 Promise,它将被等待;在此之前,恢复被视为正在进行中。
  • onError?: () => Promise<unknown> | unknown
    • 可选
    • 在恢复期间发生错误时将被调用
    • 如果返回 Promise,它将被等待

useIsRestoring

如果您正在使用 PersistQueryClientProvider,您也可以与它一起使用 useIsRestoring hook 来检查当前是否正在进行恢复。 useQuery 及其同类项也在内部检查此项,以避免恢复和挂载查询之间的竞态条件。

持久化器

持久化器接口

持久化器具有以下接口

tsx
export interface Persister {
  persistClient(persistClient: PersistedClient): Promisable<void>
  restoreClient(): Promisable<PersistedClient | undefined>
  removeClient(): Promisable<void>
}
export interface Persister {
  persistClient(persistClient: PersistedClient): Promisable<void>
  restoreClient(): Promisable<PersistedClient | undefined>
  removeClient(): Promisable<void>
}

持久化的 Client 条目具有以下接口

tsx
export interface PersistedClient {
  timestamp: number
  buster: string
  clientState: DehydratedState
}
export interface PersistedClient {
  timestamp: number
  buster: string
  clientState: DehydratedState
}

您可以导入它们(以构建持久化器)

tsx
import {
  PersistedClient,
  Persister,
} from '@tanstack/react-query-persist-client'
import {
  PersistedClient,
  Persister,
} from '@tanstack/react-query-persist-client'

构建持久化器

您可以按任何您喜欢的方式持久化。以下是一个如何构建 Indexed DB 持久化器的示例。与 Web Storage API 相比,Indexed DB 更快,存储容量超过 5MB,并且不需要序列化。这意味着它可以轻松存储 JavaScript 原生类型,例如 DateFile

tsx
import { get, set, del } from 'idb-keyval'
import {
  PersistedClient,
  Persister,
} from '@tanstack/react-query-persist-client'

/**
 * Creates an Indexed DB persister
 * @see https://mdn.org.cn/en-US/docs/Web/API/IndexedDB_API
 */
export function createIDBPersister(idbValidKey: IDBValidKey = 'reactQuery') {
  return {
    persistClient: async (client: PersistedClient) => {
      await set(idbValidKey, client)
    },
    restoreClient: async () => {
      return await get<PersistedClient>(idbValidKey)
    },
    removeClient: async () => {
      await del(idbValidKey)
    },
  } satisfies Persister
}
import { get, set, del } from 'idb-keyval'
import {
  PersistedClient,
  Persister,
} from '@tanstack/react-query-persist-client'

/**
 * Creates an Indexed DB persister
 * @see https://mdn.org.cn/en-US/docs/Web/API/IndexedDB_API
 */
export function createIDBPersister(idbValidKey: IDBValidKey = 'reactQuery') {
  return {
    persistClient: async (client: PersistedClient) => {
      await set(idbValidKey, client)
    },
    restoreClient: async () => {
      return await get<PersistedClient>(idbValidKey)
    },
    removeClient: async () => {
      await del(idbValidKey)
    },
  } satisfies Persister
}