3.1 缓存是 TanStack Query 的灵魂
很多人学 TanStack Query 卡在"为什么它有时重取有时不重取"。答案全在两个时间配置——staleTime 和 gcTime。它们控制着数据从"新鲜"到"陈旧"再到"被垃圾回收"的完整生命周期。
3.2 核心概念
- staleTime
- 数据保持"新鲜(fresh)"的时长,默认
0(即立刻陈旧)。新鲜期内即使触发条件满足也不会后台重取,直接用缓存。设为 5 分钟意味着 5 分钟内绝不重取。 - gcTime (旧名 cacheTime)
- 查询"无人订阅"后,缓存数据在内存里保留多久才被垃圾回收,默认
5 分钟。组件卸载后 key 变 inactive,超过 gcTime 才真正清除。v5 把 cacheTime 更名为 gcTime。 - stale-while-revalidate
- 核心策略:数据陈旧时,先立即返回缓存让 UI 秒渲染(不闪 loading),同时后台发请求拿最新数据,回来后无缝替换。用户几乎无感。
- fresh / stale
- fresh:距上次获取未超过 staleTime,用缓存不重取。stale:已超过 staleTime,满足触发条件时会后台重取。这是"是否需要同步"的判断。
- active / inactive
- active:至少有一个组件在用(挂载)这个 query。inactive:没有组件订阅(组件都卸载了)。inactive 的查询才会进入 gcTime 倒计时。
- refetch 触发条件
- 数据 stale 时,以下时机会自动后台重取:组件挂载(refetchOnMount)、窗口重新聚焦(refetchOnWindowFocus)、网络重连(refetchOnReconnect),外加可选的定时轮询 refetchInterval。
3.3 staleTime 与 gcTime 的关系
两者独立、作用于不同阶段,一图理清:
// 时间轴(假设 staleTime=1min, gcTime=5min)
// 0s 请求成功,数据 fresh
// 0~60s fresh — 任何触发都不重取,直接用缓存
// 60s+ stale — 组件挂载/聚焦/重连会后台重取
// 组件卸载 query 变 inactive,开始 gcTime 倒计时
// 卸载后 5min gcTime 到,缓存被回收,下次是全新 loading
一句话区分
staleTime 管"要不要重新请求",gcTime 管"要不要保留内存"。前者影响网络行为,后者影响内存占用。通常 gcTime ≥ staleTime。
3.4 配置 staleTime
// 单个查询
useQuery({
queryKey: ["config"],
queryFn: fetchConfig,
staleTime: 5 * 60 * 1000, // 5 分钟内不重取
});
// 全局默认(v5 支持 staleTime: Infinity 永不过期)
new QueryClient({
defaultOptions: { queries: { staleTime: 30_000 } },
});
3.5 控制后台刷新时机
三个 refetchOn* 开关默认都为 true(数据 stale 时生效)。可按需关闭:
useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
refetchOnWindowFocus: false, // 切回标签页不重取
refetchOnReconnect: true, // 断网重连后重取
refetchOnMount: "always", // 每次挂载都重取(忽略 stale)
refetchInterval: 10_000, // 每 10s 轮询一次
});
refetchOnWindowFocus 的争议
默认开启的"切回页面就刷新"对实时性强的应用很棒,但也可能造成意外请求(比如切到 DevTools 再切回)。仪表盘类应用建议保留,表单编辑页建议关闭以免覆盖用户输入。
3.6 演示:staleTime 如何影响行为
| 场景 | staleTime=0(默认) | staleTime=5min |
|---|---|---|
| 组件挂载 | 后台重取 | 用缓存,不重取 |
| 切回标签页 | 后台重取 | 不重取 |
| 另一组件用同 key | 共享缓存 + 后台重取 | 共享缓存,不重取 |
| 网络请求次数 | 较多(保证新鲜) | 较少(省流量) |
3.7 手动读写与检查缓存
import { useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
// 读取当前缓存(不触发请求)
const cached = queryClient.getQueryData(["todos"]);
// 查看某 query 的元信息
const state = queryClient
.getQueryCache()
.find({ queryKey: ["todos"] })?.state;
console.log(state?.dataUpdatedAt, state?.isInvalidated);
3.8 选择合适的 staleTime
- 几乎不变的数据(国家列表、枚举):
staleTime: Infinity,只在手动失效时重取。 - 常规业务数据(用户资料、文章):
1~5 分钟,平衡新鲜度和请求量。 - 高实时数据(股价、消息):
0+refetchInterval轮询,或用 WebSocket 推送后 setQueryData。
3.9 小结与下一步
- staleTime 控制"重不重取",gcTime 控制"留不留内存",二者独立。
- stale-while-revalidate:先给缓存秒渲染,再后台同步最新数据。
- 数据 stale 时,挂载/聚焦/重连会触发后台刷新,可逐项配置。
- 按数据变化频率选 staleTime:静态用 Infinity,实时用 0+轮询。
- 下一章讲 分页与无限加载——keepPreviousData 与 useInfiniteQuery。