persistQueryClient
这是一组用于与“持久化器”(persisters)交互的工具,这些持久化器可将你的 queryClient 保存下来供后续使用。你可以使用不同的 持久化器 将客户端及缓存存储到多种不同的存储层中。
构建持久化器
工作原理
重要提示:为使持久化功能正常工作,你很可能需要向 QueryClient 传入一个 gcTime 值,以在注水(hydration)期间覆盖默认值(如上所示)。
若在创建 QueryClient 实例时未设置该值,则其默认为 300000(5 分钟)用于注水,且存储的缓存将在 5 分钟无活动后被丢弃。这是默认的垃圾回收行为。
该值应设为与 persistQueryClient 的 maxAge 选项相同或更高。例如,若 maxAge 为 24 小时(默认值),则 gcTime 应设为 24 小时或更高。若低于 maxAge,垃圾回收机制将提前触发,导致缓存比预期更早被丢弃。
你也可以传入 Infinity 来完全禁用垃圾回收行为。
受 JavaScript 限制,允许的最大 gcTime 值约为 24 天,但可通过 timeoutManager.setTimeoutProvider 绕过此限制。
1const queryClient = new QueryClient({2 defaultOptions: {3 queries: {4 gcTime: 24 * 60 * 60 * 1000, // 24 hours5 },6 },7})缓存失效(Cache Busting)
有时,你可能会对应用或数据做出变更,从而立即使所有已缓存数据失效。一旦发生这种情况,你可以传入一个 buster 字符串选项。若找到的缓存不包含该失效字符串,则会被丢弃。以下多个函数均接受此选项:
1persistQueryClient({ queryClient, persister, buster: buildHash })2persistQueryClientSave({ queryClient, persister, buster: buildHash })3persistQueryClientRestore({ queryClient, persister, buster: buildHash })移除
若发现数据属于下列任一情况:
- 已过期(参见
maxAge) - 已失效(参见
buster) - 出错(例如:
throws ...) - 为空(例如:
undefined)
则会调用持久化器的 removeClient() 方法,缓存将被立即丢弃。
API
persistQueryClientSave
- 你的查询(query)/修改(mutation)将被
dehydrated并由你提供的持久化器存储。 createSyncStoragePersister和createAsyncStoragePersister对该操作进行节流,确保每秒最多执行一次,以避免频繁写入带来的开销。请查阅其文档了解如何自定义节流时间。
你可以使用它在你选定的时刻显式地持久化缓存。
1persistQueryClientSave({2 queryClient,3 persister,4 buster = '',5 dehydrateOptions = undefined,6})persistQueryClientSubscribe
每当你的 queryClient 缓存发生变化时,即运行 persistQueryClientSave。例如:你可能在用户登录并勾选“记住我”时启动 subscribe。
- 它返回一个
unsubscribe函数,可用于停止监控,从而终止对持久化缓存的更新。 - 若你想在
unsubscribe后清除已持久化的缓存,可向persistQueryClientRestore传入一个新的buster,这将触发持久化器的removeClient方法并丢弃已持久化的缓存。
1persistQueryClientSubscribe({2 queryClient,3 persister,4 buster = '',5 dehydrateOptions = undefined,6})persistQueryClientRestore
- 尝试从持久化器中
hydrate之前已持久化的脱水(dehydrated)查询/修改缓存,并将其还原至所传入 query client 的查询缓存中。 - 若找到的缓存早于
maxAge(默认为 24 小时),则会被丢弃。该时限可根据需要自定义。
你可以使用它在你选定的时刻恢复缓存。
1persistQueryClientRestore({2 queryClient,3 persister,4 maxAge = 24 * 60 * 60 * 1000, // 24 hours5 buster = '',6 hydrateOptions = undefined,7})persistQueryClient
执行以下操作:
- 立即恢复任何已持久化的缓存(参见
persistQueryClientRestore) - 订阅查询缓存,并返回
unsubscribe函数(参见persistQueryClientSubscribe)。
此功能继承自 3.x 版本。
1persistQueryClient({2 queryClient,3 persister,4 maxAge = 24 * 60 * 60 * 1000, // 24 hours5 buster = '',6 hydrateOptions = undefined,7 dehydrateOptions = undefined,8})Options
所有可用选项如下:
1interface PersistQueryClientOptions {2 /** The QueryClient to persist */3 queryClient: QueryClient4 /** The Persister interface for storing and restoring the cache5 * to/from a persisted location */6 persister: Persister7 /** The max-allowed age of the cache in milliseconds.8 * If a persisted cache is found that is older than this9 * time, it will be **silently** discarded10 * (defaults to 24 hours) */11 maxAge?: number12 /** A unique string that can be used to forcefully13 * invalidate existing caches if they do not share the same buster string */14 buster?: string15 /** The options passed to the hydrate function16 * Not used on `persistQueryClientSave` or `persistQueryClientSubscribe` */17 hydrateOptions?: HydrateOptions18 /** The options passed to the dehydrate function19 * Not used on `persistQueryClientRestore` */20 dehydrateOptions?: DehydrateOptions21}实际上提供了三个接口:
PersistedQueryClientSaveOptions用于persistQueryClientSave和persistQueryClientSubscribe(不使用hydrateOptions)。PersistedQueryClientRestoreOptions用于persistQueryClientRestore(不使用dehydrateOptions)。PersistQueryClientOptions用于persistQueryClient
在 Preact 中使用
persistQueryClient 将尝试恢复缓存,并自动订阅后续变更,从而将你的客户端与所提供的存储同步。
但恢复是异步的,因为所有持久化器本质上都是异步的。这意味着,若你在恢复过程中渲染 App,而某个查询恰好在此时挂载并发起拉取,则可能引发竞态条件。
此外,若你在 Preact 组件生命周期之外订阅变更,则无法取消订阅:
1// 🚨 never unsubscribes from syncing2persistQueryClient({3 queryClient,4 persister: localStoragePersister,5})6 7// 🚨 happens at the same time as restoring8PreactDOM.createRoot(rootElement).render(<App />)PersistQueryClientProvider
针对此场景,你可以使用 PersistQueryClientProvider。它将确保根据 Preact 组件生命周期正确执行订阅与取消订阅,并确保在恢复完成前,查询不会开始拉取。查询仍会正常渲染,但会处于 fetchingState: 'idle' 状态,直至数据恢复完成。之后,除非恢复的数据足够 新鲜(fresh),否则将重新拉取;同时也会尊重 initialData。它可替代常规的 QueryClientProvider 使用:
1import { PersistQueryClientProvider } from '@tanstack/preact-query-persist-client'2import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'3 4const queryClient = new QueryClient({5 defaultOptions: {6 queries: {7 gcTime: 24 * 60 * 60 * 1000, // 24 hours8 },9 },10})11 12const persister = createAsyncStoragePersister({13 storage: window.localStorage,14})15 16PreactDOM.createRoot(rootElement).render(17 <PersistQueryClientProvider18 client={queryClient}19 persistOptions={{ persister }}20 >21 <App />22 </PersistQueryClientProvider>,23)属性(Props)
PersistQueryClientProvider 接收与 QueryClientProvider 相同的属性,并额外支持:
persistOptions: PersistQueryClientOptions- 所有可传给 persistQueryClient 的 选项,但不包括 QueryClient 本身
onSuccess?: () => Promise<unknown> | unknown- 可选
- 初始恢复完成后调用
- 可用于 resumePausedMutations
- 若返回 Promise,则会等待其完成;在此期间,恢复过程被视为仍在进行中
onError?: () => Promise<unknown> | unknown- 可选
- 恢复过程中抛出错误时调用
- 若返回 Promise,则会等待其完成
useIsRestoring
若你正在使用 PersistQueryClientProvider,还可配合使用 useIsRestoring Hook 来检查当前是否正在进行恢复。useQuery 及其相关方法内部也检查此状态,以避免恢复过程与挂载查询之间的竞态条件。
持久化器(Persisters)
持久化器接口
持久化器具有以下接口:
1export interface Persister {2 persistClient(persistClient: PersistedClient): Promisable<void>3 restoreClient(): Promisable<PersistedClient | undefined>4 removeClient(): Promisable<void>5}已持久化的客户端条目具有以下接口:
1export interface PersistedClient {2 timestamp: number3 buster: string4 clientState: DehydratedState5}你可以导入这些类型(用于构建持久化器):
1import {2 PersistedClient,3 Persister,4} from '@tanstack/preact-query-persist-client'构建持久化器
你可以按需实现任意持久化方式。以下是一个构建 Indexed DB 持久化器的示例。相比 Web Storage API,Indexed DB 更快、可存储超过 5MB 数据,且无需序列化。这意味着它可直接存储 JavaScript 原生类型,例如 Date 和 File。
1import { get, set, del } from 'idb-keyval'2import {3 PersistedClient,4 Persister,5} from '@tanstack/preact-query-persist-client'6 7/**8 * Creates an Indexed DB persister9 * @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API10 */11export function createIDBPersister(idbValidKey: IDBValidKey = 'reactQuery') {12 return {13 persistClient: async (client: PersistedClient) => {14 await set(idbValidKey, client)15 },16 restoreClient: async () => {17 return await get<PersistedClient>(idbValidKey)18 },19 removeClient: async () => {20 await del(idbValidKey)21 },22 } satisfies Persister23}