首页
/ TanStack Query Angular 中 Filters 完全指南:QueryFilters 与 MutationFilters 的使用与源码原理

TanStack Query Angular 中 Filters 完全指南:QueryFilters 与 MutationFilters 的使用与源码原理

2026-09-05 23:28:00作者:胡唯隽

在 TanStack Query(含 Angular 集成包 @tanstack/angular-query-experimental)中,cancelQueriesremoveQueriesrefetchQueriesisMutatingQueryClient 方法都接受一个 QueryFiltersMutationFilters 对象作为参数。本篇技术指南基于仓库中的 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 FiltersQueryFilters):用于按 queryKey、激活状态、新鲜度、拉取状态等条件匹配 Query,类型定义位于 utils.ts
  • Mutation FiltersMutationFilters):用于按 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。
  • 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 源码 可以看到这些属性是如何组合生效的:

  1. 默认值type 未传时默认 'all',这解释了文档中"Defaults to all"的行为;
  2. queryKey 匹配:当 exact: true 时,比较的是 query.queryHashhashQueryKeyByOptions(queryKey, query.options) 的结果(utils.ts#L147-L155)——注意精确匹配时使用的是目标 query 自身的 queryKeyHashFn 对过滤 key 重新哈希后再比较,因此自定义了 queryKeyHashFn 的 query 也能正确精确匹配;当 exactfalse 或未设置时,走 partialMatchKey(query.queryKey, queryKey)包含式前缀匹配
  3. type 过滤:调用 query.isActive() 判断激活状态,active 要求激活、inactive 要求未激活;
  4. stale 过滤:通过 query.isStale() !== stale 判断,注意这里只有显式传 truefalse 才生效;
  5. fetchStatus 过滤:直接比较 query.state.fetchStatus
  6. predicate 兜底:所有条件通过后才执行 predicate(query) 作为最终过滤器,任何一项失败立即返回 false

其中 partialMatchKeyutils.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 等方法的入参类型 RefetchQueryFiltersInvalidateQueryFilters 都是对 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。
  • status?: MutationStatus
    • 允许按 mutation 的状态(如 pendingsuccesserror)进行过滤。
  • predicate?: (mutation: Mutation) => boolean
    • 该谓词函数作为最终过滤器作用于所有已匹配的 mutations。如果没有指定其他过滤器,该函数将针对缓存中的每一条 mutation 求值。

matchMutation 的两个关键边界行为

阅读 matchMutation 源码 可以得到两个文档未展开、但实践中重要的事实:

  1. 没有 mutationKey 的 mutation 不会被任何 mutationKey 过滤器命中:源码在匹配前先检查 if (!mutation.options.mutationKey) { return false }utils.ts#L188-L190)。也就是说,如果你在 mutationOptions 中未设置 mutationKey,就无法通过 key 过滤器定位它,只能依赖 statuspredicate
  2. exact 匹配基于 key 哈希exact: true 时通过 hashKey(mutation.options.mutationKey) !== hashKey(mutationKey) 判断(utils.ts#L191-L194),默认的 hashKey 会对对象 key 的键名排序后再 JSON.stringifyutils.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

这两个函数的过滤逻辑与 QueryCacheMutationCache 内部的 findAll / find 完全同源——缓存按 key 索引先粗筛、再调用 match 函数细筛(见 queryCache.tsmutationCache.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' 覆盖不到的维度,适合写更精确的缓存诊断逻辑。

参考

登录后查看全文
热门项目推荐
相关项目推荐