查询失效与刷新
本教程共 38 篇 · 第 11 篇 · 更新于 2026-07-27 · 约 8 分钟阅读
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'] })
}
}
失效会做两件事:
- 标记为 stale — 覆盖任何 staleTime 配置,强制算过期
- 触发后台重取 — 如果查询有 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'] })
},
})
流程是:
- 用户点「新增」
mutate触发,mutationFn发 POST 请求- 请求成功,触发
onSuccess invalidateQueries把['todos']标记为 stale- 因为列表组件还在用这个查询,触发后台重取
- 新数据回来,列表自动更新
用户感知:点一下按钮,列表很快就多了新数据。
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,让相关查询自动刷新。下一篇讲更激进的刷新方式—乐观更新。