TanStack Query 缓存失效模式
TanStack Query 的四个缓存操作:cancelQueries、setQueryData、removeQueries、invalidateQueries 的使用场景和区别。
#type / howto
#status / growing
#tech / dev / frontend
#resource / react
[!info] related notes
- 所属 MOC: TanStack Query 知识地图
- 前置: TanStack Query 服务端状态
- 模式: queryKey Factory 模式
- 实践: TanStack Query 与 SSE 流式数据集成
- 迁移: 从 useState 迁移到 TanStack Query
- 关联: Mutation 完整生命周期
TanStack Query 缓存失效模式
核心问题
TanStack Query 提供了多个缓存操作 API,但它们的语义不同,用错会导致数据不一致或缓存泄漏。
四个 API 的分工
cancelQueries — 取消在途请求
await queryClient.cancelQueries({
queryKey: ['consultation', 'session', id],
});
语义:停止正在飞行中的网络请求,防止旧请求后返回覆盖新数据。
使用场景:
- 乐观更新前(onMutate 里)
- SSE 写 cache 前
- 任何需要”先停旧的,再写新的”的场景
setQueryData — 直接写缓存
queryClient.setQueryData(
['consultation', 'conversations'],
{ conversations: [] },
);
语义:不发网络请求,直接用你提供的数据覆盖缓存。
使用场景:
- 删除后立即清空列表(乐观更新)
- SSE 增量更新
- 新建后预填缓存
- mutation 拿到响应后直接更新
removeQueries — 删除缓存条目
queryClient.removeQueries({
queryKey: ['consultation', 'conversation', deletedId],
});
语义:从缓存中彻底移除这个 queryKey 对应的数据。下次组件用到时会重新请求。
使用场景:
- 删除单个资源后,清除它的详情缓存
- 删除全部资源后,清除所有详情缓存
- 避免缓存泄漏(大量不再需要的缓存条目)
注意:removeQueries 不会触发重新请求,只是删除缓存。如果组件还在用这个 queryKey,下次挂载时才会重新请求。
invalidateQueries — 标记缓存过期
queryClient.invalidateQueries({
queryKey: ['consultation', 'session', id],
});
语义:做两件事:
- 把缓存标记为 stale(过期)
- 如果这个 queryKey 当前有活跃的
useQuery订阅者 → 立即在后台 refetch - 如果没有活跃订阅者 → 不发请求,下次有人用到时再请求
使用场景:
- mutation 成功后,让相关缓存自动刷新
- SSE 结束后,做最终数据对齐
- 用户手动触发刷新
决策指南:invalidate vs setQueryData
遇到”mutation 后如何更新缓存”时,按这个顺序判断:
① 已经知道完整的更新结果?
→ 是 → setQueryData(直接写缓存,零延迟)
→ 否 → 继续
② 不确定后端最终结果?
→ 是 → invalidateQueries(让后端决定最新数据)
→ 否 → 继续
③ 需要两者兼顾?
→ setQueryData 乐观更新(立即反映到 UI)
→ invalidateQueries 最终对齐(确保和后端一致)
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 重命名 title | setQueryData | 已知新 title,不需要请求 |
| 置顶 pinned | setQueryData | 已知新状态 |
| 删除会话 | setQueryData + removeQueries | 从列表移除 + 删除详情缓存 |
| 诊断分析 | invalidateQueries | 需要后端返回分析结果 |
| 生成治疗方案 | invalidateQueries | 需要后端返回完整方案 |
| SSE titleGenerated | setQueryData | 已知新 title |
| SSE extractedInfoUpdate | setQueryData | 增量更新,流结束后再 invalidate |
| 创建新资源 | setQueryData(预填)或 invalidateQueries | 取决于创建接口是否返回完整数据 |
使用场景速查表
| 场景 | API | 说明 |
|---|---|---|
| 删除资源后清空详情缓存 | removeQueries | 彻底移除,避免缓存泄漏 |
| 删除资源后更新列表 | setQueryData | 立即从列表中移除 |
| SSE 增量更新 | cancelQueries + setQueryData | 先取消旧请求再写入 |
| mutation 成功后刷新 | invalidateQueries | 标记过期,自动重新请求 |
| 乐观更新前 | cancelQueries | 防止旧请求覆盖乐观数据 |
| 预填新建资源的缓存 | setQueryData | 避免 loading 闪烁 |
常见错误
removeQueries 误删目标
// ❌ predicate 可能误删
queryClient.removeQueries({
queryKey: consultationKeys.all,
predicate: (q) => q.queryKey.length > 1,
});
// consultationKeys.conversations() 的 key 长度也是 2,会被误删
// ✅ 精确删除
queryClient.removeQueries({
queryKey: [...consultationKeys.all, 'conversation'],
});
queryClient.removeQueries({
queryKey: [...consultationKeys.all, 'session'],
});
忘记 cancelQueries
// ❌ 直接写 cache,可能被旧请求覆盖
queryClient.setQueryData(key, newData);
// ✅ 先取消再写
await queryClient.cancelQueries({ queryKey: key });
queryClient.setQueryData(key, newData);
混淆 removeQueries 和 invalidateQueries
removeQueries:删缓存,不触发请求invalidateQueries:标记过期,触发请求
删除资源用 removeQueries(不需要再请求已删除的数据)。修改资源用 invalidateQueries(需要获取最新数据)。