框架
版本

persistQueryClient

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

构建持久化程序

工作原理

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

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

它应设置为与 persistQueryClient 的 maxAge 选项相同或更高的值。例如,如果 maxAge 为 24 小时(默认值),则 gcTime ��为 24 小时或更高。如果低于 maxAge,垃圾回收将介入并比预期更早地丢弃存储的缓存。

您也可以将其传递为 Infinity 以完全禁用垃圾回收行为。

由于 Javascript 的限制,允许的最大 gcTime 约为 24 天(请参阅更多)。

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

缓存清除

有时,您可能对应用程序或数据进行了更改,从而立即使所有缓存数据失效。如果发生这种情况,您可以传递一个 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

  • 您的查询/变更将被脱水并由您提供的持久化程序存储。
  • createSyncStoragePersistercreateAsyncStoragePersister 会将此操作限制为最多每秒发生一次,以节省可能昂贵的写入操作。请查看它们的文档以了解如何自定义其节流时间。

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

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

persistQueryClientSubscribe

每当 queryClient 的缓存发生更改时,都会运行 persistQueryClientSave。例如:您可能会在用户登录并选中“记住我”时启动 subscribe

  • 它返回一个 unsubscribe 函数,您可以使用该函数停止监视;从而结束对持久化缓存的更新。
  • 如果要在 unsubscribe 后清除持久化缓存,可以将新的 buster 发送到 persistQueryClientRestore,这将触发持久化程序的 removeClient 函数并丢弃持久化缓存。
tsx
persistQueryClientSubscribe({
  queryClient,
  persister,
  buster = '',
  dehydrateOptions = undefined,
})
persistQueryClientSubscribe({
  queryClient,
  persister,
  buster = '',
  dehydrateOptions = undefined,
})

persistQueryClientRestore

  • 尝试将先前持久化的脱水查询/变更缓存从持久化程序水合回传递的查询客户端的查询缓存中。
  • 如果找到的缓存早于 maxAge(默认为 24 小时),则将其丢弃。此时间可以根据您的需要进行自定义。

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

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

persistQueryClient

执行以下操作:

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

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

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

选项

所有可用选项如下:

tsx
interface PersistQueryClientOptions {
  /** 要持久化的 QueryClient */
  queryClient: QueryClient
  /** 用于将缓存存储和恢复到/从持久化位置的 Persister 接口 */
  persister: Persister
  /** 缓存的最大允许存在时间(以毫秒为单位)。
   * 如果找到的持久化缓存早于此
   * 时间,则将其**静默**丢弃
   * (默认为 24 小时) */
  maxAge?: number
  /** 一个唯一字符串,可用于强制
   * 使现有缓存失效(如果它们不共享相同的 buster 字符串) */
  buster?: string
  /** 传递给水合函数的选项
   * 未在 `persistQueryClientSave` 或 `persistQueryClientSubscribe` 上使用 */
  hydrateOptions?: HydrateOptions
  /** 传递给脱水函数的选项
   * 未在 `persistQueryClientRestore` 上使用 */
  dehydrateOptions?: DehydrateOptions
}
interface PersistQueryClientOptions {
  /** 要持久化的 QueryClient */
  queryClient: QueryClient
  /** 用于将缓存存储和恢复到/从持久化位置的 Persister 接口 */
  persister: Persister
  /** 缓存的最大允许存在时间(以毫秒为单位)。
   * 如果找到的持久化缓存早于此
   * 时间,则将其**静默**丢弃
   * (默认为 24 小时) */
  maxAge?: number
  /** 一个唯一字符串,可用于强制
   * 使现有缓存失效(如果它们不共享相同的 buster 字符串) */
  buster?: string
  /** 传递给水合函数的选项
   * 未在 `persistQueryClientSave` 或 `persistQueryClientSubscribe` 上使用 */
  hydrateOptions?: HydrateOptions
  /** 传递给脱水函数的选项
   * 未在 `persistQueryClientRestore` 上使用 */
  dehydrateOptions?: DehydrateOptions
}

实际上有三个可用的接口:

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

与 React 一起使用

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

但是,恢复是异步的,因为所有持久化程序本质上都是异步的,这意味着如果您在恢复时渲染您的应用程序,如果查询同时挂载和获取,则可能会遇到竞争条件。

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

tsx
// 🚨 从不同步中永不取消订阅
persistQueryClient({
  queryClient,
  persister: localStoragePersister,
})

// 🚨 与恢复同时发生
ReactDOM.createRoot(rootElement).render(<App />)
// 🚨 从不同步中永不取消订阅
persistQueryClient({
  queryClient,
  persister: localStoragePersister,
})

// 🚨 与恢复同时发生
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 小时
    },
  },
})

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 小时
    },
  },
})

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

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

属性

PersistQueryClientProvider 接受与 QueryClientProvider 相同的属性,此外还有:

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

useIsRestoring

如果您正在使用 PersistQueryClientProvider,则还可以使用 useIsRestoring 钩子来检查当前是否正在进行恢复。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>
}

持久化客户端条目具有以下接口:

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'

/**
 * 创建一个 Indexed DB 持久化程序
 * @see https://developer.mozilla.org/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'

/**
 * 创建一个 Indexed DB 持久化程序
 * @see https://developer.mozilla.org/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
}