TanStack Query Angular 中 Filters 完全指南:QueryFilters 与 MutationFilters 的使用与源码原理
在 TanStack Query(含 Angular 集成包 @tanstack/angular-query-experimental)中,cancelQueries、removeQueries、refetchQueries、isMutating 等 QueryClient 方法都接受一个 QueryFilters 或 MutationFilters 对象作为参数。本篇技术指南基于仓库中的 Angular Filters 文档 及其关联的 Filters 指南,完整讲解这两类过滤器的全部属性、典型用法,并结合 query-core 源码 深入剖析 matchQuery / matchMutation 的匹配逻辑,帮助你精准控制"哪些 Query/Mutation 会被取消、移除、重新拉取或统计"。
过滤器机制总览
Filters 的本质是"对缓存中已存在的 Query 或 Mutation 按条件做谓词匹配"。所有匹配逻辑都集中在框架无关的 @tanstack/query-core 中实现,因此无论使用 React Query、Vue Query 还是 Angular 的 experimental 包(见 angular-query-experimental 包),过滤语义完全一致:
- Query Filters(
QueryFilters):用于按queryKey、激活状态、新鲜度、拉取状态等条件匹配 Query,类型定义位于 utils.ts; - Mutation Filters(
MutationFilters):用于按mutationKey、状态等条件匹配 Mutation,类型定义位于 utils.ts。
在 Angular 应用中,QueryClient 由应用根级统一提供(见 providers.ts),因此这些过滤器同样适用于在 Service、拦截器回调或 Mutation 的 onSuccess 中调用 queryClient.refetchQueries(...) 等场景。
Query Filters:匹配 Query 的完整属性说明
过滤器对象的核心用法示例:
// Cancel all queries
await queryClient.cancelQueries()
// Remove all inactive queries that begin with `posts` in the key
queryClient.removeQueries({ queryKey: ['posts'], type: 'inactive' })
// Refetch all active queries
await queryClient.refetchQueries({ type: 'active' })
// Refetch all active queries that begin with `posts` in the key
await queryClient.refetchQueries({ queryKey: ['posts'], type: 'active' })
一个 QueryFilters 对象支持以下属性:
queryKey?: QueryKey- 设置该属性以定义要匹配的 query key。
exact?: boolean- 如果你不希望按 query key 做包含式(前缀)搜索,可传入
exact: true,只返回与你传入的 query key 完全精确匹配的那条 query。
- 如果你不希望按 query key 做包含式(前缀)搜索,可传入
type?: 'active' | 'inactive' | 'all'- 默认为
all; - 设置为
active时匹配激活状态(有观察者)的 queries; - 设置为
inactive时匹配未激活的 queries。
- 默认为
stale?: boolean- 设置为
true时匹配 stale(过期)queries; - 设置为
false时匹配 fresh(新鲜)queries。
- 设置为
fetchStatus?: FetchStatus- 设置为
fetching时匹配当前正在拉取的 queries; - 设置为
paused时匹配"想要拉取但被暂停"的 queries; - 设置为
idle时匹配未在拉取的 queries。
- 设置为
predicate?: (query: Query) => boolean- 该谓词函数作为最终过滤器作用于所有已匹配的 queries。如果没有指定其他过滤器,该函数将针对缓存中的每一条 query 求值。
matchQuery 的源码实现细节
从 matchQuery 源码 可以看到这些属性是如何组合生效的:
- 默认值:
type未传时默认'all',这解释了文档中"Defaults to all"的行为; - queryKey 匹配:当
exact: true时,比较的是query.queryHash与hashQueryKeyByOptions(queryKey, query.options)的结果(utils.ts#L147-L155)——注意精确匹配时使用的是目标 query 自身的queryKeyHashFn对过滤 key 重新哈希后再比较,因此自定义了queryKeyHashFn的 query 也能正确精确匹配;当exact为false或未设置时,走partialMatchKey(query.queryKey, queryKey)的包含式前缀匹配; - type 过滤:调用
query.isActive()判断激活状态,active要求激活、inactive要求未激活; - stale 过滤:通过
query.isStale() !== stale判断,注意这里只有显式传true或false才生效; - fetchStatus 过滤:直接比较
query.state.fetchStatus; - predicate 兜底:所有条件通过后才执行
predicate(query)作为最终过滤器,任何一项失败立即返回false。
其中 partialMatchKey(utils.ts#L239-L269)是包含式匹配的核心:它对两个 key 递归逐元素比较,b(过滤器 key)可以是 a(实际 key)的前缀,即 queryKey: ['posts'] 能命中 ['posts', 1]、['posts', { id: 1 }] 等,但不会命中 ['posts', 1, 'comments'] 之外的更短 key。这正是文档示例中"begin with posts in the key"的实现基础。
另外,QueryTypeFilter 类型 定义为 'all' | 'active' | 'inactive';而 refetchQueries / invalidateQueries 等方法的入参类型 RefetchQueryFilters、InvalidateQueryFilters 都是对 QueryFilters 的扩展(见 types.ts#L627-L635)。
Mutation Filters:匹配 Mutation 的完整属性说明
Mutation 过滤器对象的使用示例:
// Get the number of all fetching mutations
await queryClient.isMutating()
// Filter mutations by mutationKey
await queryClient.isMutating({ mutationKey: ['post'] })
// Filter mutations using a predicate function
await queryClient.isMutating({
predicate: (mutation) => mutation.state.variables?.id === 1,
})
一个 MutationFilters 对象支持以下属性:
mutationKey?: MutationKey- 设置该属性以定义要匹配的 mutation key。
exact?: boolean- 如果你不希望按 mutation key 做包含式搜索,可传入
exact: true,只返回与你传入的 mutation key 完全精确匹配的那条 mutation。
- 如果你不希望按 mutation key 做包含式搜索,可传入
status?: MutationStatus- 允许按 mutation 的状态(如
pending、success、error)进行过滤。
- 允许按 mutation 的状态(如
predicate?: (mutation: Mutation) => boolean- 该谓词函数作为最终过滤器作用于所有已匹配的 mutations。如果没有指定其他过滤器,该函数将针对缓存中的每一条 mutation 求值。
matchMutation 的两个关键边界行为
阅读 matchMutation 源码 可以得到两个文档未展开、但实践中重要的事实:
- 没有 mutationKey 的 mutation 不会被任何
mutationKey过滤器命中:源码在匹配前先检查if (!mutation.options.mutationKey) { return false }(utils.ts#L188-L190)。也就是说,如果你在mutationOptions中未设置mutationKey,就无法通过 key 过滤器定位它,只能依赖status或predicate; - exact 匹配基于 key 哈希:
exact: true时通过hashKey(mutation.options.mutationKey) !== hashKey(mutationKey)判断(utils.ts#L191-L194),默认的hashKey会对对象 key 的键名排序后再JSON.stringify(utils.ts#L223-L234),因此{ a: 1, b: 2 }与{ b: 2, a: 1 }在精确匹配下视为相同 key。
Utils:matchQuery 与 matchMutation 工具函数
除了通过 QueryClient 方法间接使用过滤器,core 包还直接导出两个匹配工具函数,可用于自定义逻辑(例如在 DevTools 扩展或自定义缓存管理中判断某条 query 是否属于某个过滤器集合):
matchQuery
const isMatching = matchQuery(filters, query)
返回一个布尔值,指示某条 query 是否匹配给定的一组 query 过滤器。实现见 utils.ts#L134-L180。
matchMutation
const isMatching = matchMutation(filters, mutation)
返回一个布尔值,指示某条 mutation 是否匹配给定的一组 mutation 过滤器。实现见 utils.ts#L182-L209。
这两个函数的过滤逻辑与 QueryCache、MutationCache 内部的 findAll / find 完全同源——缓存按 key 索引先粗筛、再调用 match 函数细筛(见 queryCache.ts 与 mutationCache.ts),因此你在自定义代码中使用 matchQuery 得到的结果与 queryClient.getQueriesData(filters) 等 API 的筛选行为保持一致。相关单测可参考 utils.test.tsx 中对 matchQuery / matchMutation 的覆盖。
Angular 场景下的实战建议
结合 Angular 生态的使用方式,几个可落地的模式:
- 按路由/页面维度清理:路由切换时调用
queryClient.removeQueries({ queryKey: ['user', id], type: 'inactive' }),只移除该用户维度下已无观察者消费的缓存,避免误删仍在使用中的查询; - 批量失效刷新:在 Mutation 成功后
queryClient.invalidateQueries({ queryKey: ['posts'], exact: true }),只刷新列表本身而不牵连['posts', id]等子维度查询(包含式匹配默认会命中它们); - 统计进行中的变更:在 Angular 组件或 Service 中轮询/订阅
queryClient.isMutating({ mutationKey: ['upload'] })来驱动全局"上传中"指示器;注意前提是这些 mutation 都设置了mutationKey,否则只能依赖predicate(结合mutation.state.variables判断,如文档示例中的variables?.id === 1); - 注意 paused 状态:
fetchStatus: 'paused'可以捞出"想拉取但被暂停"的 queries(例如网络恢复前的场景),这是type: 'active'覆盖不到的维度,适合写更精确的缓存诊断逻辑。
参考
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00