首页
/ TanStack Query(Preact Query)useIsMutating Hook 全面解析:统计进行中的 Mutation 并构建全局加载指示器

TanStack Query(Preact Query)useIsMutating Hook 全面解析:统计进行中的 Mutation 并构建全局加载指示器

2026-09-08 11:23:06作者:咎竹峻Karen

导读

useIsMutating@tanstack/preact-query 提供的一个可选 Hook,用于返回应用中当前正处于 pending(进行中/fetching) 状态的 mutation 数量。它最常见的应用场景是渲染“保存中 / 提交中……”之类的应用级全局加载指示器——当页面同时存在多个表单或按钮触发 mutation 时,任何变更尚未落定都会让这个数值大于 0。阅读本文后,你将掌握该 Hook 的完整类型签名、过滤参数的含义与默认行为、在 Preact 应用中的接入方式,以及其底层依赖 MutationCacheuseMutationState 的计数与订阅原理,并可通过仓库内的单元测试验证这些行为。

本文以 useIsMutating.md 为主干,结合 preact-queryquery-core 源码展开。

类型签名与核心作用

preact-query/src/useMutationState.ts:35 中,该 Hook 的类型签名如下:

function useIsMutating(filters?, queryClient?): number;

其语义为:返回你的应用中正在“fetching”(处于 pending 状态)的 mutation 数量。这里的数量是跨所有组件共享的——无论 mutation 是在哪个组件内通过 useMutation 触发的,只要它们位于同一个 QueryClientMutationCache 中,就会被统一计数,因此它特别适合做应用级的加载指示器,而不仅是某个按钮局部的 loading 状态。

需要特别说明的是:“fetching”这一表述在源码层面对应的是 mutation 的 status === 'pending'。也就是说,这个数字反映的是正在执行(尚未 resolve/reject)的 mutation 数量,而不是处于 idlesuccesserror 状态的数量。

参数详解

filters?(可选)

类型为 MutationFilters<unknown, Error, unknown, unknown>

用于缩小被统计的 mutation 范围。MutationFilters 完整接口定义在 packages/query-core/src/utils.ts:54,包含四个可选字段:

字段 类型 作用说明
exact boolean 是否要求 mutation key 与给定的 key 完全相等。默认 false,即默认采用前缀匹配
predicate (mutation) => boolean 传入一个判定函数,对每个候选 mutation 进行任意自定义筛选
mutationKey MutationKey 或其元组前缀 只统计 mutation key 能匹配给定 key 的 mutation
status MutationStatus 按状态过滤,MutationStatus 取值为 'idle' | 'pending' | 'success' | 'error'(见 packages/query-core/src/types.ts:1078

useIsMutating 内部会把你传入的 filters 再强制叠加一层 status: 'pending' 过滤(稍后源码一节会证明),所以即使你手动传入了 status,最终也只统计进行中的 mutation。

queryClient?(可选)

类型为 QueryClient

用于显式指定一个自定义 QueryClient。若不传,则使用最近上下文(即最靠近当前组件的 QueryClientProvider)中提供的实例。关于 Provider 的接入方式可参考 QueryClientProvider.mduseQueryClient.md

返回值

返回类型为 number,即当前处于 pending 状态的 mutation 数量。组件会随数量变化而响应式重渲染;当没有匹配的 mutation 时返回 0,因此可直接用真值判断驱动 UI。

官方示例:按 mutationKey 前缀统计

下面是文档中给出的完整示例——统计所有 key 前缀命中 ['posts'] 的、正在进行的 mutation:

import { useIsMutating } from '@tanstack/preact-query'

function PostsMutatingIndicator() {
  // How many mutations matching the posts prefix are in progress?
  const isMutatingPosts = useIsMutating({ mutationKey: ['posts'] })

  return isMutatingPosts ? <span>Saving posts...</span> : null
}

这个写法建立在默认前缀匹配规则之上:任何通过 useMutation({ mutationKey: ['posts', ...] })mutationKeyFn 产生 ['posts', id] 这类 key 的 mutation,只要处于 pending 状态,都会让 isMutatingPosts1

底层实现原理:useMutationState + MutationCache

一层薄封装:强制 pending 过滤并取数组长度

useIsMutating 的实现本身非常精简——它就是对 useMutationState 的一层封装。源码位于 packages/preact-query/src/useMutationState.ts:35

export function useIsMutating(
  filters?: MutationFilters,
  queryClient?: QueryClient,
): number {
  const client = useQueryClient(queryClient)
  return useMutationState(
    { filters: { ...filters, status: 'pending' } },
    client,
  ).length
}

从源码可以清晰地看到两个关键事实:

  1. { ...filters, status: 'pending' }:传入的过滤条件被展开后,强行覆盖 status'pending',确保 useIsMutating 的返回值只代表“进行中”的 mutation 数量。
  2. 取返回数组的 .lengthuseMutationState 返回与过滤条件匹配的 mutation 状态数组,useIsMutating 直接取其长度。

两者在 packages/preact-query/src/index.ts:52 中一并作为公开 API 从包入口导出。

MutationCache 的匹配与订阅机制

useMutationState 的完整实现位于同一文件 useMutationState.ts:157,其数据来源是 QueryClient 上的 MutationCache

const mutationCache = useQueryClient(queryClient).getMutationCache()

初次渲染时通过 getResult(mutationCache, options) 立刻计算出结果快照,其内部调用 mutationCache.findAll(options.filters)(对应 packages/query-core/src/mutationCache.ts:219)并逐条映射出 mutation 状态;随后通过 mutationCache.subscribe(...) 监听缓存变更,并借助 replaceEqualDeep 做深比较,仅在结果实际变化时才通过 notifyManager.schedule 调度重渲染。最终由 useSyncExternalStore 把订阅桥接到 Preact 的响应式渲染周期。也就是说,一旦任何 mutation 状态翻转(例如 pending → success),计数就会实时更新并驱动使用该 Hook 的组件重渲染。

匹配规则:exact / predicate / mutationKey / status 如何生效

真正决定“哪些 mutation 被统计”的是 query-core 中的 matchMutation 函数,位于 packages/query-core/src/utils.ts:182

export function matchMutation(
  filters: MutationFilters,
  mutation: Mutation<any, any>,
): boolean {
  const { exact, status, predicate, mutationKey } = filters
  if (mutationKey) {
    if (!mutation.options.mutationKey) {
      return false
    }
    if (exact) {
      if (hashKey(mutation.options.mutationKey) !== hashKey(mutationKey)) {
        return false
      }
    } else if (!partialMatchKey(mutation.options.mutationKey, mutationKey)) {
      return false
    }
  }
  if (status && mutation.state.status !== status) return false
  if (predicate && !predicate(mutation)) return false
  return true
}

值得注意的匹配语义:

  • mutationKeyexact:未设置 exact: true 时采用 partialMatchKey 做前缀匹配;设置 exact: true 时则通过 hashKey 对 key 做稳定哈希后严格比对,二者哈希结果必须一致。
  • status:要求 mutation 的 state.status 与给定状态一致。
  • predicate:你可以在谓词中拿到 mutation 实例,进而访问 mutation.optionsmutation.state 等做任意判断。

实战场景与进阶用法

场景一:应用级“保存中”指示器

当全局有多处表单、多处导出任务同时提交时,可用一个放置在布局层(Layout)的指示器统一表达“仍有变更在路上”:

import { useIsMutating } from '@tanstack/preact-query'

function AppBar() {
  const isMutating = useIsMutating()

  return (
    <header>
      {isMutating > 0 && (
        <div role="status" aria-live="polite">
          {isMutating} 个操作正在保存...
        </div>
      )}
    </header>
  )
}

不传 filters 时统计的是整个 MutationCache 中所有 pending mutation,适合希望全局感知的场合。

场景二:按业务域过滤(精确匹配)

若只想针对某一个精确 key 统计,可传入 exact: true

const isPublishingPost = useIsMutating({
  mutationKey: ['posts', 'publish'],
  exact: true,
})

场景三:自定义 predicate 过滤

利用 predicate 可结合 mutation 的 meta 或 options 做复杂筛选,例如只统计某个 scope 下的 mutation:

const isMutating = useIsMutating({
  predicate: (mutation) => mutation.state.status === 'pending'
    && mutation.options.scope?.id === 'dashboard',
})

场景四:指定自定义 QueryClient

在测试环境或需要隔离 client 的多实例场景中,可通过第二个参数传入独立的 QueryClient,见 packages/preact-query/src/tests/useMutationState.test.tsx:156 中的对应测试:

const queryClient = new QueryClient()

function Page() {
  const isMutating = useIsMutating({}, queryClient)
  // ...
}

用单元测试验证计数行为

仓库中针对 useIsMutating 的行为有完整的测试覆盖,位于 packages/preact-query/src/tests/useMutationState.test.tsx,可以当作权威的行为规范来阅读:

测试一:计数随 mutation 生命周期变化(见同文件 第 18 行)。测试先后触发两个不同 key、耗时不同的 mutation,断言 isMutatingArray 依次为 [0, 1, 2, 1, 0]——第一个 mutation 启动时计数为 1,第二个启动时变为 2,随两个 mutation 依次完成而逐步降回 10。同时测试注释指出,由于内部批处理策略,中间状态也可能出现 [0, 1, 2, 1, 0] 之外如多一次 1 的过渡取值,边界情况下的瞬时取值不被视为稳定契约

测试二:按 mutationKey 过滤(见 第 81 行)。两个 mutation 分别使用 mutationKey1mutationKey2,但 Hook 只统计 mutationKey1,因此数组断言为 [0, 1, 0]

测试三:按 predicate 过滤(见 第 117 行)。仅凭 mutation key 前缀无法区分二者时,通过 predicate 比对 mutation.options.mutationKey?.[0] 完成筛选,同样得到 [0, 1, 0]

测试四:自定义 QueryClient 生效(见 第 156 行)。在不包裹 Provider 的渲染中显式传入 queryClient 后,mutation 触发期间页面文本稳定显示 mutating: 1

与相关 API 的关系与差异

useIsMutatinguseMutationState 的面向场景封装。两者的对应关系可归纳为:

维度 useIsMutating useMutationState
返回值 匹配的 pending mutation 数量number 匹配 mutation 的状态数组
过滤 filters 上强制叠加 status: 'pending' 提供 filtersselect,可选择任意状态与字段
典型用途 全局 / 局部“进行中”指示器 读取具体 mutation 的 datavariables

例如,若你需要展示“哪些 post 正在保存”,应改用 useMutationStateselect 抽取变量;若你只关心数量,useIsMutating 更简洁。更完整的能力可参考 useMutationState.mduseMutation.md;与查询侧对应的“正在请求的 query 数量”统计则见 useIsFetching.md

小结

  • useIsMutating(filters?, queryClient?) 返回当前 pending mutation 的数量,实现位于 packages/preact-query/src/useMutationState.ts:35
  • 它本质上是 useMutationState 的薄封装:在 filters 上强制注入 status: 'pending' 后取其结果数组 .length
  • 数据来自 QueryClientMutationCache,通过 findAll 匹配、subscribe 监听,配合 replaceEqualDeepnotifyManager 实现最小化重渲染。
  • 匹配规则由 packages/query-core/src/utils.ts:182matchMutation 统一实现:mutationKey 默认前缀匹配、exact 精确匹配、statuspredicate 可叠加使用。
  • 若项目使用 React、Solid、Vue、Svelte 或 Angular 版本,其对应适配层同样基于相同的 MutationFiltersMutationCache 语义,理解本文原理后可平滑迁移。

通过将 useIsMutating 置于布局层并配合 mutationKey 的规范化组织,你可以在几乎零额外开销的前提下,为整个 Preact 应用提供准确、实时的全局提交反馈。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389