先修:QueryClient、浏览器存储、缓存新鲜度。持久化把部分内存缓存写入 localStorage 或其他存储,在下一次启动恢复;它不保证接口可离线写入,也不等于服务器的权威数据库。
| 配置 | 控制的问题 |
|---|---|
| staleTime | 恢复的数据是否需要重新获取 |
| gcTime | 不活跃查询在内存中保留多久 |
| maxAge | 磁盘快照允许恢复的最大年龄 |
使用整缓存持久化时,gcTime 通常应不小于 maxAge,否则缓存可能比预期更早被回收。schema 改变时使用 buster 丢弃旧结构。官方持久化指南
安装与 Query v5 兼容的 @tanstack/react-query-persist-client 和 @tanstack/query-sync-storage-persister。本例用于浏览器 SPA;SSR 应使用框架支持的初始化流程,不能跨请求共享该模块的 client。
// src/persisted-provider.tsx
import type { ReactNode } from 'react';
import { QueryClient } from '@tanstack/react-query';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
const day = 24 * 60 * 60 * 1000;
const client = new QueryClient({
defaultOptions: { queries: { staleTime: 60_000, gcTime: day } },
});
function getStorage(): Storage | undefined {
try { return typeof window === 'undefined' ? undefined : window.localStorage; }
catch { return undefined; }
}
const persister = createSyncStoragePersister({
storage: getStorage(),
key: 'public-query-cache',
});
export function PersistedProvider({ children }: { children: ReactNode }) {
return <PersistQueryClientProvider
client={client}
persistOptions={{
persister,
maxAge: day,
buster: 'public-schema-v1',
dehydrateOptions: {
shouldDehydrateQuery: (query) =>
query.state.status === 'success' && query.meta?.persist === true,
},
}}
>{children}</PersistQueryClientProvider>;
}只有明确设置 meta.persist:true 的成功查询才进入快照。示例没有实际查询,需用 Provider 包裹应用并在允许持久化的查询中声明 meta。不要用布尔开关代替对敏感字段的审查。
Provider 协调恢复与订阅,恢复期间避免查询抢先自动执行;需要独立恢复提示时可使用 @tanstack/react-query 导出的 useIsRestoring。恢复中可以显示不依赖数据的外壳,不必阻塞整个应用。
localStorage 访问可能被浏览器限制,写入也可能因配额失败。缓存不可用时应允许正常联网查询,不能让启动过程永久卡住。序列化结果可能被用户或扩展修改,恢复数据不能成为授权依据。
私有数据应尽量不持久化。确需按账号保存时,设计账号隔离的键、登出清除内存与磁盘的流程,并处理仍在进行的旧账号请求,避免清理后又写回旧数据。
函数本身不能随缓存序列化。恢复暂停 mutation 时,需要为对应 mutationKey 提供默认 mutationFn,再按流程 resumePausedMutations,并处理失败、重试和服务端幂等。不能只加一个 onSuccess 回调就宣称拥有可靠离线队列。
按条目 persister 是另一种方案,适合按访问恢复较大缓存。其 API 可能带 experimental 标识,应锁定版本并验证升级;先掌握整缓存流程,再评估是否需要更复杂的恢复策略。
只对一条公开列表启用持久化。首次加载后等待写入,刷新并观察恢复与后台请求;修改 buster 后再次刷新,应忽略旧快照。再清空存储或模拟写入失败,应用仍应可联网使用。
验收:能分别解释 staleTime、gcTime、maxAge,磁盘没有秘密数据,恢复失败有回退路径。不要仅凭刷新后“内容还在”就认定离线一致性已经完成。