Chapter 03

缓存机制

吃透 staleTime 与 gcTime 的区别、stale-while-revalidate 策略,以及后台刷新到底何时触发。

3.1 缓存是 TanStack Query 的灵魂

很多人学 TanStack Query 卡在"为什么它有时重取有时不重取"。答案全在两个时间配置——staleTimegcTime。它们控制着数据从"新鲜"到"陈旧"再到"被垃圾回收"的完整生命周期。

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

3.9 小结与下一步