首页 / TanStack 生态入门教程 / 查询失效与刷新

TanStack 生态入门教程

查询失效与刷新

本教程共 38 篇 · 第 11 篇 · 更新于 2026-07-27 · 约 8 分钟阅读

TanStackTanStack 生态入门教程invalidateQueries查询失效refetchQueriesremoveQueries缓存刷新失效联动

11. 查询失效与刷新

本节目标:掌握 invalidateQueries 的失效机制和各种匹配方式,搞懂 refetchQueries、removeQueries 的区别,学会 Mutation 成功后刷新查询的标准做法,学完能让数据修改后自动同步。

11.1 为什么要手动失效

Query 的缓存有自己的过期机制(staleTime),但有时候你知道数据已经变了,等它自然过期太慢。

比如用户新增了一条 todo,列表数据立刻就过时了。与其等 staleTime 到期,不如主动告诉 Query:「这个查询的数据旧了,重新请求吧」。

这就是失效(Invalidation)—手动把缓存标记为过期,触发重新请求。

11.2 invalidateQueries 基础

queryClient.invalidateQueries 是最常用的失效方法:

import { useQueryClient } from '@tanstack/react-query'

function MyComponent() {
  const queryClient = useQueryClient()

  const handleClick = () => {
    // 失效所有查询
    queryClient.invalidateQueries()

    // 失效所有以 'todos' 开头的查询
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  }
}

失效会做两件事:

  1. 标记为 stale — 覆盖任何 staleTime 配置,强制算过期
  2. 触发后台重取 — 如果查询有 active 观察者(组件在用),自动重新请求
Note

失效不等于删除。失效只是标记「数据旧了」,缓存数据还在。后台请求成功后用新数据覆盖;请求失败的话旧数据还在,用户不会看到空白。这是 stale-while-revalidate 的思路。

11.3 前缀匹配

invalidateQueries 默认是前缀匹配。这和 QueryKey 的层级设计配合,能批量失效一类查询:

queryClient.invalidateQueries({ queryKey: ['todos'] })

// 下面这些都会被失效:
useQuery({ queryKey: ['todos'], ... })                    // 命中
useQuery({ queryKey: ['todos', { page: 1 }], ... })      // 命中
useQuery({ queryKey: ['todos', 5], ... })                // 命中
useQuery({ queryKey: ['todos', 5, 'comments'], ... })    // 命中

// 这些不会被失效:
useQuery({ queryKey: ['posts'], ... })                   // 不命中

这就是为什么强调 QueryKey 第一项要放稳定的资源名。一个 ['todos'] 就能失效所有 todo 相关查询,不管带什么参数。

11.4 精确匹配:exact

只想失效某一个具体查询,加 exact: true

queryClient.invalidateQueries({
  queryKey: ['todos'],
  exact: true,
})

// 命中:
useQuery({ queryKey: ['todos'], ... })

// 不命中:
useQuery({ queryKey: ['todos', { page: 1 }], ... })  // 不命中
useQuery({ queryKey: ['todos', 5], ... })            // 不命中

11.5 按变量匹配

传更具体的 key,能匹配带特定变量的查询:

queryClient.invalidateQueries({
  queryKey: ['todos', { type: 'done' }],
})

// 命中:
useQuery({ queryKey: ['todos', { type: 'done' }], ... })

// 不命中:
useQuery({ queryKey: ['todos'] })                    // 不命中
useQuery({ queryKey: ['todos', { type: 'todo' }] })  // 不命中

11.6 predicate 自定义匹配

更复杂的条件用 predicate 函数:

queryClient.invalidateQueries({
  predicate: (query) =>
    query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10,
})

// 命中:
useQuery({ queryKey: ['todos', { version: 20 }] })  // 命中
useQuery({ queryKey: ['todos', { version: 10 }] })  // 命中

// 不命中:
useQuery({ queryKey: ['todos', { version: 5 }] })   // 不命中

predicate 会对缓存里每个查询执行一遍,返回 true 的才失效。灵活但注意性能,查询特别多时别频繁用。

11.7 其他刷新方法

除了 invalidateQueries,还有几个相关方法。

refetchQueries:直接重取

invalidateQueries 是「标记过期,有观察者才重取」。refetchQueries 是「不管有没有观察者,直接重取」:

// 重取所有 active 查询
await queryClient.refetchQueries({ type: 'active' })

// 重取所有以 'posts' 开头的查询
await queryClient.refetchQueries({ queryKey: ['posts'] })

区别在于:没有组件在用的查询,invalidateQueries 不会重取(只标记),refetchQueries 会强行重取。

Tip

大多数场景用 invalidateQueries 就够。refetchQueries 适合那种「即使没人看也要刷新」的场景,比如后台同步。

removeQueries:直接删除

想把缓存彻底删掉,用 removeQueries

// 删除所有以 'posts' 开头的查询
queryClient.removeQueries({ queryKey: ['posts'] })

// 删除所有 inactive 查询
queryClient.removeQueries({ type: 'inactive' })

删除后,下次有组件用到这个 key 会重新请求,期间显示 loading。

常见用途:用户登出时清掉所有业务数据缓存:

function logout() {
  queryClient.removeQueries()
  // 或者更精确:queryClient.clear()
}

resetQueries:重置

resetQueries 是「删除 + 重置到初始状态」的组合。对有 initialData 的查询,会重置回 initialData:

queryClient.resetQueries({ queryKey: ['todos'] })

cancelQueries:取消

取消正在进行的查询,前面讲过:

await queryClient.cancelQueries({ queryKey: ['todos'] })

11.8 失效与变更联动

这是最经典的使用模式:Mutation 成功后,失效相关查询

import { useMutation, useQueryClient } from '@tanstack/react-query'

const queryClient = useQueryClient()

const mutation = useMutation({
  mutationFn: addTodo,
  onSuccess: () => {
    // 新增成功后,失效 todo 列表
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

流程是:

  1. 用户点「新增」
  2. mutate 触发,mutationFn 发 POST 请求
  3. 请求成功,触发 onSuccess
  4. invalidateQueries['todos'] 标记为 stale
  5. 因为列表组件还在用这个查询,触发后台重取
  6. 新数据回来,列表自动更新

用户感知:点一下按钮,列表很快就多了新数据。

await 失效

onSuccess 里如果 await 失效操作,能让 mutation 保持 pending 状态直到刷新完成:

const mutation = useMutation({
  mutationFn: addTodo,
  onSuccess: async () => {
    // 等待失效和重取完成
    await queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

这样 isPending 会一直 true 到数据刷新完,UI 上的 loading 状态更连贯。

失效多个查询

一次操作可能影响多个查询:

const mutation = useMutation({
  mutationFn: addTodo,
  onSuccess: async () => {
    await Promise.all([
      queryClient.invalidateQueries({ queryKey: ['todos'] }),
      queryClient.invalidateQueries({ queryKey: ['reminders'] }),
    ])
  },
})

Promise.all 并行失效,比串行快。

Warning

别一有 mutation 就 invalidateQueries() 不传 key,这样会失效所有查询,可能导致一堆不必要的请求。精确指定要失效的 key。

11.9 直接更新缓存 vs 失效

有时候 mutation 的返回值就是新数据,没必要再请求一遍。这时可以用 setQueryData 直接更新缓存,跳过失效+重取:

const mutation = useMutation({
  mutationFn: editTodo,
  onSuccess: (data) => {
    // 直接用返回值更新缓存
    queryClient.setQueryData(['todo', { id: data.id }], data)
  },
})

这种做法省一次请求,适合「修改单个资源,返回值就是完整新资源」的场景。列表查询还是得失效,因为 setQueryData 改不了列表里的某一项。

11.10 小结

失效相关的几个方法:

方法作用
invalidateQueries标记 stale,有观察者才重取
refetchQueries强制重取,不管有没有观察者
removeQueries彻底删除缓存
resetQueries重置到 initialData
cancelQueries取消进行中的请求
setQueryData直接写缓存(不重取)

最常用的模式:mutation 的 onSuccess 里调 invalidateQueries,让相关查询自动刷新。下一篇讲更激进的刷新方式—乐观更新。