experimental_createQueryPersister
安装
该工具作为一个独立包提供,可通过 '@tanstack/query-persist-client-core' 导入。
1npm install @tanstack/query-persist-client-core或
1pnpm add @tanstack/query-persist-client-core或
1yarn add @tanstack/query-persist-client-core或
1bun add @tanstack/query-persist-client-core注意:该工具也包含在
@tanstack/react-query-persist-client包中,因此如果你已使用该包,则无需单独安装。
使用方法
- 导入
experimental_createQueryPersister函数; - 创建一个新的
experimental_createQueryPersister实例;- 你可以向其传入任意符合
AsyncStorage接口的storage—— 下方示例使用了 React Native 的 async-storage;
- 你可以向其传入任意符合
- 将该
persister作为选项传给你的查询。这可以通过将其传给QueryClient的defaultOptions,或传给任意useQueryHook 实例来实现;- 若将该
persister作为defaultOptions传入,则所有查询都会被持久化到所提供的storage中。你还可以通过传入filters进一步缩小范围。与persistClient插件不同,此方式不会将整个查询客户端作为一个条目持久化,而是分别持久化每个查询;键值使用查询哈希(query hash); - 若将该
persister传给单个useQueryHook,则仅该查询会被持久化;
- 若将该
- 注意:
queryClient.setQueryData()操作不会被持久化,这意味着如果你执行了乐观更新,并在查询失效前刷新页面,则对查询数据所做的更改将会丢失。参见 https://github.com/TanStack/query/issues/6310。
通过这种方式,你无需存储整个 QueryClient,而是可自主选择应用程序中哪些内容值得持久化。每个查询均按需恢复(即首次使用时)和持久化(每次 queryFn 执行后),因此无需节流处理。恢复查询后,staleTime 仍会被尊重:若数据被视为 stale,则恢复后会立即重新拉取;若数据为 fresh,则 queryFn 不会运行。
从内存中垃圾回收一个查询 不会 影响其已持久化的数据。这意味着查询可在内存中保留更短时间,从而提升 内存效率;下次使用时,它们将直接从持久化存储中恢复。
1import AsyncStorage from '@react-native-async-storage/async-storage'2import { QueryClient } from '@tanstack/react-query'3import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'4 5const persister = experimental_createQueryPersister({6 storage: AsyncStorage,7 maxAge: 12 * 60 * 60 * 1000, // 12 hours8})9 10const queryClient = new QueryClient({11 defaultOptions: {12 queries: {13 gcTime: 30 * 1000, // 30 seconds14 persister: persister.persisterFn,15 },16 },17})调整后的默认值
createPersister 插件在技术上封装了 queryFn,因此若 queryFn 不运行,则不会触发恢复。由此,它在查询与网络之间充当缓存层。因此,当启用持久化器时,networkMode 默认设为 'offlineFirst',以便即使在无网络连接的情况下,也能从持久化存储中恢复数据。
其他工具函数
调用 experimental_createQueryPersister 除返回 persisterFn 外,还会返回额外的工具函数,便于你在用户代码中实现自定义功能。
persistQueryByKey(queryKey: QueryKey, queryClient: QueryClient): Promise<void>
该函数会将 Query 持久化至创建持久化器时指定的存储及键值。
该工具可与 setQueryData 配合使用,在不等待失效处理的前提下,将乐观更新结果持久化至存储。
1const persister = experimental_createQueryPersister({2 storage: AsyncStorage,3 maxAge: 12 * 60 * 60 * 1000, // 12 hours4})5 6const queryClient = useQueryClient()7 8useMutation({9 mutationFn: updateTodo,10 onMutate: async (newTodo) => {11 ...12 // Optimistically update to the new value13 queryClient.setQueryData(['todos'], (old) => [...old, newTodo])14 // And persist it to storage15 persister.persistQueryByKey(['todos'], queryClient)16 ...17 },18})retrieveQuery<T>(queryHash: string): Promise<T | undefined>
该函数尝试根据 queryHash 检索已持久化的查询。
若 query 为 expired、busted 或 malformed,则对应条目将从存储中移除,并返回 undefined。
已持久化的条目必须具备字符串类型的 queryHash、数组类型的 queryKey,以及一个包含有限数值型 dataUpdatedAt 的 state 对象。不满足这些要求的条目将在检索、垃圾回收、恢复及过滤删除过程中被自动移除,且不影响其他条目的处理流程。null 仍为有效的查询数据。
persisterGc(): Promise<void>
该函数可用于定期清理存储中 expired、busted 或 malformed 的条目。
要使该函数正常工作,你的存储必须暴露 entries 方法,该方法需返回一个 key-value tuple array。
例如:Object.entries(localStorage) 对应 localStorage,或 entries 来自 idb-keyval。
restoreQueries(queryClient: QueryClient, filters): Promise<void>
该函数可用于恢复当前由持久化器存储的查询。
例如,当应用在离线模式下启动,或你希望上一 session 中全部或特定数据能立即可用(而无需经历中间 loading 状态)时。
筛选对象支持以下属性:
queryKey?: QueryKey- 设置该属性以指定需匹配的查询键;
exact?: boolean- 若你不希望按查询键进行包含式搜索,可传入
exact: true选项,仅返回与所传查询键完全匹配的查询。
- 若你不希望按查询键进行包含式搜索,可传入
要使该函数正常工作,你的存储必须暴露 entries 方法,该方法需返回一个 key-value tuple array。
例如:Object.entries(localStorage) 对应 localStorage,或 entries 来自 idb-keyval。
removeQueries(filters): Promise<void>
使用 queryClient.removeQueries 时,数据仍保留在持久化器中,需单独移除。
该函数可用于移除当前由持久化器存储的查询。
筛选对象支持以下属性:
queryKey?: QueryKey- 设置该属性以指定需匹配的查询键;
exact?: boolean- 若你不希望按查询键进行包含式搜索,可传入
exact: true选项,仅返回与所传查询键完全匹配的查询。
- 若你不希望按查询键进行包含式搜索,可传入
要使该函数正常工作,你的存储必须暴露 entries 方法,该方法需返回一个 key-value tuple array。
例如:Object.entries(localStorage) 对应 localStorage,或 entries 来自 idb-keyval。
API
experimental_createQueryPersister
1experimental_createQueryPersister(options: StoragePersisterOptions)Options
1export interface StoragePersisterOptions {2 /** The storage client used for setting and retrieving items from cache.3 * For SSR pass in `undefined`.4 */5 storage: AsyncStorage | Storage | undefined | null6 /**7 * How to serialize the data to storage.8 * @default `JSON.stringify`9 */10 serialize?: (persistedQuery: PersistedQuery) => string11 /**12 * How to deserialize the data from storage.13 * @default `JSON.parse`14 */15 deserialize?: (cachedString: string) => PersistedQuery16 /**17 * A unique string that can be used to forcefully invalidate existing caches,18 * if they do not share the same buster string19 */20 buster?: string21 /**22 * The max-allowed age of the cache in milliseconds.23 * If a persisted cache is found that is older than this24 * time, it will be discarded25 * @default 24 hours26 */27 maxAge?: number28 /**29 * Prefix to be used for storage key.30 * Storage key is a combination of prefix and query hash in a form of `prefix-queryHash`.31 */32 prefix?: string33 /**34 * If set to `true`, the query will refetch on successful query restoration if the data is stale.35 * If set to `false`, the query will not refetch on successful query restoration.36 * If set to `'always'`, the query will always refetch on successful query restoration.37 * Defaults to `true`.38 */39 refetchOnRestore?: boolean | 'always'40 /**41 * Filters to narrow down which Queries should be persisted.42 */43 filters?: QueryFilters44}45 46interface AsyncStorage<TStorageValue = string> {47 getItem: (key: string) => MaybePromise<TStorageValue | undefined | null>48 setItem: (key: string, value: TStorageValue) => MaybePromise<unknown>49 removeItem: (key: string) => MaybePromise<void>50 entries?: () => MaybePromise<Array<[key: string, value: TStorageValue]>>51}默认选项为:
1{2 prefix = 'tanstack-query',3 maxAge = 24 * 60 * 60 * 1000,4 serialize = JSON.stringify,5 deserialize = JSON.parse,6 refetchOnRestore = true,7}