我们不断破坏 TanStack Query 的缓存失效——于是我们停止手动管理它
发布: (2026年1月3日 GMT+8 11:27)
4 min read
原文: Dev.to
Source: Dev.to
真实的痛点(来自生产环境)
- 手动编写缓存键容易出错
- 失效逻辑难以推理
- 生成的 Hook 并不能解决缓存一致性问题
- 团队内部缺乏统一的标准
这些问题往往在数周后以陈旧的 UI bug 形式显现。
思路:停止手动创建缓存键
我们希望:
- 为缓存键提供唯一的真相来源
- 实现可预测的失效行为
- 与生成的 Hook 无缝集成
规则: 缓存键和失效逻辑应由系统生成,而非手写。
流程(文档化的)
Query Cache Flow 将许多团队已经在使用的流程形式化为:
- REST API – 缓存行为成为契约的一部分,而不是事后考虑。
- 核心原语:
createQueryGroupCRUD– 所有操作都从查询组开始。
核心原语:createQueryGroupCRUD
import { createQueryGroupCRUD } from '@/queries'
export const accountsQueryGroup = createQueryGroupCRUD('accounts')
这行代码定义了:
- 与 accounts 相关的所有查询键
- CRUD 操作如何使相关查询失效
- 列表与详情的统一结构
没有字符串字面量,没有重复。
与生成的 Hook 一起使用
只需一次性用稳定的查询键包装生成的 Hook:
export const useAccounts = () =>
generatedUseAccounts({
query: {
queryKey: [accountsQueryGroup.list.queryKey],
},
})
- 查询键来源于查询组。
- 每个使用者都采用相同的键结构。
- 没有组件自行 invent(发明)键。
变更与自动失效
变更后:
await createAccount.mutateAsync(data)
使用组预定义的键进行失效:
invalidateQueriesForKeys([
accountsQueryGroup.create.invalidates,
])
失效键已经知道哪些列表和关联查询需要刷新,能够自动形成级联,而无需重复逻辑。
为什么它比随意失效更好
典型的随意做法:
queryClient.invalidateQueries({ queryKey: ['accounts'] })
queryClient.invalidateQueries({ queryKey: ['accounts', id] })
问题:
- 键在各处被复制。
- 容易忘记某个键。
- 难以审查或重构。
使用查询组:
- 失效意图明确。
- 行为集中管理。
- 审查变得轻而易举。
这种模式能为你带来什么
- 一致的缓存键结构
- 自动级联失效
- 类型安全的缓存操作
- 与代码生成 Hook 的一流集成
最重要的是: 你再也不需要思考缓存键。
何时适合使用这种模式
适用于:
- 使用 OpenAPI + 代码生成的项目
- 列表/详情关系
- 多个变更影响同一数据
- 长期可维护性
如果应用非常小,额外的开销可能不值得,但随着规模增长,这种模式的收益会逐渐显现。
这是一种模式,而非魔法
Query Cache Flow 不:
- 替代对 TanStack Query 的理解
- 隐藏失效机制的工作方式
- 修复设计不佳的 API
它把缓存行为形式化,使其不再是隐式知识。
最后思考
缓存失效众所周知地困难,但大多数痛点来源于缺乏结构,而不是 React Query 本身。把缓存键和失效视为一等的、由系统生成的产物,会让问题变得无聊——而无聊正是我们想要的状态。
文档: