
TanStack Query 异步持久化实战createAsyncStoragePersister 完整指南【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query本指南基于 TanStack Query 仓库中的createAsyncStoragePersister官方文档结合tanstack/query-async-storage-persister包的源码实现系统讲解如何将 React Query 的查询缓存异步写入任意存储层如 React Native 的 AsyncStorage、window.localStorage 等实现离线缓存与启动即恢复。读完本文你将掌握该 Persister 的安装、配置、重试策略、节流原理以及它与PersistQueryClientProvider、persistQueryClient生态的完整配合方式。为什么需要异步存储持久化TanStack Query 默认将查询缓存保存在内存中页面刷新或应用重启后缓存即丢失。持久化Persistence机制负责把QueryClient中的缓存脱水dehydrate后写入存储层并在下次启动时再水化hydrate回来从而实现离线可用用户断网时仍能读取上次成功获取的数据秒开体验启动时直接渲染缓存数据避免白屏等待跨会话缓存如 React Native 应用重启后保留 24 小时的查询缓存。createAsyncStoragePersister是 TanStack Query 官方提供的异步持久化工具它把所有读写操作设计为异步 API因此既支持原生异步存储React Native AsyncStorage也兼容同步存储window.localStorage因为同步 API 天然满足异步接口的调用约定。安装createAsyncStoragePersister作为独立包发布与 React 集成时还需安装tanstack/react-query-persist-client提供PersistQueryClientProvider与persistQueryClient等上层工具npm install tanstack/query-async-storage-persister tanstack/react-query-persist-client其他包管理器等价命令pnpm add tanstack/query-async-storage-persister tanstack/react-query-persist-clientyarn add tanstack/query-async-storage-persister tanstack/react-query-persist-clientbun add tanstack/query-async-storage-persister tanstack/react-query-persist-client注意官方文档同时指出旧的tanstack/query-sync-storage-persistercreateSyncStoragePersister已标记为Deprecated将在下一个大版本中移除官方建议直接改用tanstack/query-async-storage-persister详见 createSyncStoragePersister.md。基本用法使用分为三步导入createAsyncStoragePersister函数传入符合AsyncStorage接口的存储对象创建 persister用PersistQueryClientProvider组件包裹应用。官方示例React Native AsyncStorageimport AsyncStorage from react-native-async-storage/async-storage import { QueryClient } from tanstack/react-query 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 asyncStoragePersister createAsyncStoragePersister({ storage: AsyncStorage, }) const Root () ( PersistQueryClientProvider client{queryClient} persistOptions{{ persister: asyncStoragePersister }} App / /PersistQueryClientProvider ) export default Root为什么 gcTime 必须设置为 24 小时示例中gcTime被显式设置为 24 小时这一点至关重要。持久化恢复依赖 persistQueryClient.md 中描述的规则若创建QueryClient时未设置gcTime恢复阶段会使用默认值3000005 分钟即存储的缓存闲置 5 分钟后就会被垃圾回收丢弃。正确的做法是让gcTime等于或大于persistQueryClient的maxAge选项默认也是 24 小时。若gcTime小于maxAge垃圾回收会先于过期判定生效导致缓存被过早清除。你也可以将gcTime设为Infinity以完全禁用垃圾回收受 JavaScriptsetTimeout最大延迟约 24 天的限制可通过 timeoutManager.setTimeoutProvider 绕过。同步存储也能用文档明确说明同步读写存储如window.localStorage同样符合AsyncStorage接口可直接传给createAsyncStoragePersisterconst localStoragePersister createAsyncStoragePersister({ storage: window.localStorage, })这是因为AsyncStorage接口的所有方法都允许返回Promise或普通值MaybePromise同步实现天然兼容。深入理解 AsyncStorage 接口persister 对存储层只要求一个极简接口你甚至可以自行实现它来对接 IndexedDB、文件系统等任意存储interface AsyncStorageTStorageValue string { getItem: (key: string) MaybePromiseTStorageValue | undefined | null setItem: (key: string, value: TStorageValue) MaybePromiseunknown removeItem: (key: string) MaybePromisevoid entries?: () MaybePromiseArray[key: string, value: TStorageValue] }从源码 index.ts 的注释可知接口注释还给出了两个边界情况SSR 场景服务端渲染时可直接传undefined此时 persister 会退化为空操作见下文无存储时的兜底行为Android WebViewwindow.localStorage在部分 WebView 配置下可能为null需要做空值处理。entries方法是可选的用于需要枚举全部缓存键的场景persister 本身并不强制要求。Options 配置详解createAsyncStoragePersister接受一个配置对象createAsyncStoragePersister(options: CreateAsyncStoragePersisterOptions)完整的CreateAsyncStoragePersisterOptions定义如下interface CreateAsyncStoragePersisterOptions { /** 用于读写缓存的存储客户端 */ storage: AsyncStorage | undefined | null /** 存储缓存时使用的键名 */ key?: string /** 避免频繁写入存储传入毫秒数对落盘操作进行节流 */ throttleTime?: number /** 如何将数据序列化到存储 */ serialize?: (client: PersistedClient) string /** 如何从存储反序列化数据 */ deserialize?: (cachedString: string) PersistedClient /** 持久化失败时的重试策略 */ retry?: AsyncPersistRetryer }默认值源码 index.ts 中直接以参数默认值形式体现{ key REACT_QUERY_OFFLINE_CACHE, throttleTime 1000, serialize JSON.stringify, deserialize JSON.parse, }storage必选项。任何符合AsyncStorage接口的对象均可包括 React Native AsyncStorage、window.localStorage、window.sessionStorage或自定义实现。传undefined/null时 persister 的所有操作变为空操作no-op。key缓存写入存储时使用的键名默认REACT_QUERY_OFFLINE_CACHE。同一存储空间下如需隔离多份缓存如多租户应用可传不同 key。throttleTime节流间隔毫秒默认1000。目的是避免查询缓存频繁变化导致存储被高频写入官方文档称之为 localStorage spamming。从源码 index.ts 可见persistClient实际由asyncThrottle包裹persistClient: asyncThrottle( async (persistedClient) { ... }, { interval: throttleTime }, )asyncThrottle的实现位于 asyncThrottle.ts其语义是间隔期内多次调用只保留最后一次的参数执行且两次实际执行的时间差不小于interval若上一次执行尚未结束后续调用会等待其完成。对应测试 asyncThrottle.test.ts 验证了合并调用执行时长超过间隔函数抛错不影响下一次调用等关键行为其中还包含对 issue #3331 时序 bug 的回归用例。serialize / deserialize自定义数据的序列化与反序列化方式默认JSON.stringify/JSON.parse。最典型的覆盖场景是压缩存储内容localStorage单域名下通常只有约 5MB 配额当缓存数据量较大时可以结合lz-string等压缩库减小体积该用法在同仓库 createSyncStoragePersister.md 中有完整示例import { compress, decompress } from lz-string import { createAsyncStoragePersister } from tanstack/query-async-storage-persister const persister createAsyncStoragePersister({ storage: window.localStorage, serialize: (data) compress(JSON.stringify(data)), deserialize: (data) JSON.parse(decompress(data)), })注意序列化后的值需与存储类型一致默认存储值为string因此serialize返回字符串。retry持久化失败时的重试策略类型为AsyncPersistRetryer见下文Retries 重试机制。Retries 重试机制持久化可能失败——例如缓存体积超过存储空间配额。重试机制允许你优雅地处理这类错误。官方文档说明异步 persister 的重试与 createSyncStoragePersister 完全一致唯一区别是重试函数本身也可以是异步的。重试函数接收它尝试保存的persistedClient、error和errorCount并应返回一个新的PersistedClient用于再次尝试返回undefined则停止重试export type PersistRetryer (props: { persistedClient: PersistedClient error: Error errorCount: number }) PersistedClient | undefined异步版本定义见源码 index.ts返回值允许是PromisePromisableexport type AsyncPersistRetryer (props: { persistedClient: PersistedClient error: Error errorCount: number }) PromisablePersistedClient | undefined底层重试循环源码 index.ts 展示了完整的保存与重试流程const trySave async (persistedClient) { try { const serialized await serialize(persistedClient) await storage.setItem(key, serialized) return } catch (error) { return error as Error } }随后在persistClient中循环先trySave若返回错误则递增errorCount并调用retry拿到新的 client 再次保存直到成功或retry返回undefined。默认情况下未传retry出错即停止不做任何重试。预置策略removeOldestQuery官方提供开箱即用的重试策略可从tanstack/react-query-persist-client导入removeOldestQuery返回一个移除最旧查询的新PersistedClient。const asyncStoragePersister createAsyncStoragePersister({ storage: AsyncStorage, retry: removeOldestQuery, })其实现位于 retryStrategies.ts复制 mutations 与 queries 数组按state.dataUpdatedAt升序排序弹出最旧的一条 query 后返回新 client当没有可移除的查询时返回undefined终止重试。典型应用场景正是存储空间不足——通过不断丢弃最旧数据来腾出空间。持久化协议与 Persister 接口createAsyncStoragePersister返回的对象实现了统一的Persister接口定义于 persist.tsexport interface Persister { persistClient: (persistClient: PersistedClient) Promisablevoid restoreClient: () PromisablePersistedClient | undefined removeClient: () Promisablevoid }对应的三个实现index.tspersistClient将脱水后的缓存序列化并setItem写入存储经asyncThrottle节流restoreClientgetItem读取字符串后deserialize还原为PersistedClientremoveClientremoveItem删除缓存。被持久化的PersistedClient结构如下persist.tsexport interface PersistedClient { timestamp: number buster: string clientState: DehydratedState }timestamp保存时刻的时间戳用于过期判定maxAgebuster缓存失效标记见下文缓存失效 Cache BustingclientState由dehydrate(queryClient)生成的脱水状态。无存储时的兜底行为当storage为undefined/null如 SSR 场景时createAsyncStoragePersister返回一组空操作 persisterindex.tspersistClient与removeClient为nooprestoreClient恒返回undefined。这意味着在服务端不会产生任何存储读写也不会在客户端水合时误触发恢复逻辑。与 PersistQueryClientProvider 的完整协作PersistQueryClientProvider是 React 层的推荐接入方式源码 PersistQueryClientProvider.tsx。它相比直接调用persistQueryClient有两个关键优势生命周期管理按 React 组件生命周期正确订阅/取消订阅持久化避免竞态恢复完成前挂载中的 query 会进入fetchingState: idle而不会立即发起请求恢复完成后若数据足够新鲜则不重新请求否则 refetch且initialData同样会被尊重。其 Props 与QueryClientProvider一致额外支持persistOptions: PersistQueryClientOptions即 persistQueryClient 的全部选项persister、maxAge、buster、hydrateOptions、dehydrateOptions但不含queryClient本身onSuccess?: () Promiseunknown | unknown初始恢复完成时回调可配合resumePausedMutations恢复暂停的 mutation返回 Promise 会被 await恢复期间视为仍在进行onError?: () Promiseunknown | unknown恢复过程中抛出错误时回调。配合useIsRestoringhook从tanstack/react-query-persist-client导出可在组件中判断恢复是否正在进行useQuery等内部也会检查该状态以避免恢复与查询挂载的竞态。缓存失效 Cache Busting当你发布新版本、数据结构变更需要立即废弃所有缓存时可传入buster字符串persistQueryClient.md。恢复时若存储中的buster与当前传入的不一致缓存会被视为无效并丢弃const persister createAsyncStoragePersister({ storage: AsyncStorage }) // 例如 buster 使用构建哈希缓存不匹配即作废 PersistQueryClientProvider client{queryClient} persistOptions{{ persister, buster: buildHash }} App / /PersistQueryClientProvider缓存被丢弃的四种情形根据 persistQueryClient 文档与 persist.ts 源码恢复时若数据属于以下任一情形persister 的removeClient()会被调用、缓存立即丢弃过期Date.now() - timestamp maxAge默认maxAge为 24 小时被作废buster不匹配异常restoreClient或deserialize抛错开发环境下会打印错误与警告日志为空如timestamp缺失或读取结果为空。手动控制持久化时机如果不想用 Provider 自动同步也可以直接使用tanstack/query-persist-client-core暴露的底层函数persist.tspersistQueryClientSave立即脱水并保存一次缓存buster默认persistQueryClientSubscribe订阅 QueryCache 与 MutationCache 的added/removed/updated事件缓存变化即保存返回unsubscribe函数persistQueryClientRestore从存储恢复缓存maxAge默认 24 小时过期/作废则丢弃persistQueryClient等价于先 Restore 再 Subscribe返回[unsubscribe, restorePromise]元组。import { persistQueryClient } from tanstack/react-query-persist-client persistQueryClient({ queryClient, persister: asyncStoragePersister, maxAge: 1000 * 60 * 60 * 24, // 24 hours buster: , })不过在 React 应用中官方文档明确指出直接在模块顶层调用persistQueryClient存在两个隐患永远不会取消订阅恢复与首次渲染同时发生可能引发竞态。因此 React 项目应优先使用PersistQueryClientProvider。自定义 Persister以 IndexedDB 为例理解了Persister接口后你完全可以绕过createAsyncStoragePersister针对特定存储手写 persister。官方文档 persistQueryClient.md 给出了基于idb-keyval的 IndexedDB 示例IndexedDB 相比 Web Storage 更快、容量更大远超 5MB且无需序列化即可直接存储Date、File等 JS 原生类型import { get, set, del } from idb-keyval import { PersistedClient, Persister } from tanstack/react-query-persist-client export function createIDBPersister(idbValidKey: IDBValidKey reactQuery) { return { persistClient: async (client: PersistedClient) { await set(idbValidKey, client) }, restoreClient: async () { return await getPersistedClient(idbValidKey) }, removeClient: async () { await del(idbValidKey) }, } satisfies Persister }小结createAsyncStoragePersister通过极简的AsyncStorage接口抽象让 TanStack Query 的缓存持久化可以自由对接 React Native AsyncStorage、window.localStorage乃至任意自定义异步存储。其核心设计包括默认安全24 小时gcTime配套、1000ms 节流写入、REACT_QUERY_OFFLINE_CACHE默认键名可扩展serialize/deserialize支持压缩、retry支持异步重试与removeOldestQuery等预置策略SSR 友好storage传空时自动降级为空操作生态完整与PersistQueryClientProvider、persistQueryClient、useIsRestoring、buster机制无缝协作覆盖离线缓存、启动恢复、版本作废等完整场景。若需查阅相关实现与测试可深入 packages/query-async-storage-persister/src、packages/query-persist-client-core/src/persist.ts 及 PersistQueryClientProvider.tsx 继续探索。【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考