我们不断破坏 TanStack Query 的缓存失效——于是我们停止手动管理它

发布: (2026年1月3日 GMT+8 11:27)
4 min read
原文: Dev.to

Source: Dev.to

真实的痛点(来自生产环境)

  • 手动编写缓存键容易出错
  • 失效逻辑难以推理
  • 生成的 Hook 并不能解决缓存一致性问题
  • 团队内部缺乏统一的标准

这些问题往往在数周后以陈旧的 UI bug 形式显现。

思路:停止手动创建缓存键

我们希望:

  • 为缓存键提供唯一的真相来源
  • 实现可预测的失效行为
  • 与生成的 Hook 无缝集成

规则: 缓存键和失效逻辑应由系统生成,而非手写。

流程(文档化的)

Query Cache Flow 将许多团队已经在使用的流程形式化为:

  1. REST API – 缓存行为成为契约的一部分,而不是事后考虑。
  2. 核心原语: 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 本身。把缓存键和失效视为一等的、由系统生成的产物,会让问题变得无聊——而无聊正是我们想要的状态。

文档:

Back to Blog

相关文章

阅读更多 »