☰
Vue Query 的 useMutationState:在 MutationCache 之上订阅与转换全局变更状态
2026/10/12 23:39:26 网站建设 项目流程

Vue Query 的 useMutationState:在 MutationCache 之上订阅与转换全局变更状态

【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query

useMutationState是 TanStack Vue Query 提供的一个组合式 API,用于访问MutationCache中全部(或经筛选后的)mutation 状态。它适用于跨组件监控异步变更的中间状态:例如同时展示多个"提交中"的按钮、收集所有进行中 mutation 的 variables、或在某个 mutation 完成后统一刷新 UI。读完本文,你将掌握useMutationState的完整选项、MutationFilters的匹配规则、select的状态转换写法,以及其底层的响应式订阅原理,并能在 Vue 3 / Vue 2.7(vue-demi)项目中直接落地使用。

本文以仓库中的官方参考文档 docs/framework/vue/reference/useMutationState.md 为主体,结合vue-query与query-core的源码与测试用例展开。相关完整 API 还可对照参考 useMutation、useIsMutating 与 mutationOptions。

一、useMutationState 是什么:订阅 MutationCache 的"全局观测器"

与useMutation的"单点操控"不同,useMutationState更像一个全局观测器:它读的是QueryClient内部MutationCache中当前存活的所有 mutation,而不是某一个 mutation 实例。

看它的源码定义 packages/vue-query/src/useMutationState.ts:

export function useMutationState<TResult = MutationState, ...>( options: | MutationStateOptions<TResult, TMutation> | (() => MutationStateOptions<TResult, TMutation>) = {}, queryClient?: QueryClient, ): Readonly<Ref<Array<TResult>>> { const resolvedOptions = computed(() => { const newOptions = typeof options === 'function' ? options() : options return { filters: cloneDeepUnref(newOptions.filters), select: newOptions.select, } }) const mutationCache = (queryClient || useQueryClient()).getMutationCache() const state = shallowRef(getResult(mutationCache, resolvedOptions.value)) const unsubscribe = mutationCache.subscribe(() => { state.value = getResult(mutationCache, resolvedOptions.value) }) watch(resolvedOptions, () => { state.value = getResult(mutationCache, resolvedOptions.value) }) onScopeDispose(() => { unsubscribe() }) return shallowReadonly(state) }

关键实现细节(均来自该文件):

  • 数据源是MutationCache:通过(queryClient || useQueryClient()).getMutationCache()取得全局缓存。若不传queryClient,则自动使用最近上下文(provide/inject或QueryClientProvider)中的实例。
  • getResult的调用链:mutationCache.findAll(filters)先按MutationFilters过滤,再对每个匹配的 mutation 执行select(mutation);未提供select时直接返回mutation.state(见 packages/vue-query/src/useMutationState.ts)。
  • 响应式更新:返回的是shallowReadonly(shallowRef(...))。当mutationCache触发added/updated/removed等订阅事件时重新计算结果;同时watch(resolvedOptions, ...)让filters/select的变化也触发重算。
  • 生命周期:通过onScopeDispose取消订阅,因此它必须运行在setup()或 effect scope 内,否则可能造成内存泄漏(useIsMutating的开发警告也与此相关,见同一文件的useIsMutating实现)。

从源码结构看,useIsMutating实际上是useMutationState的"薄封装":它把filters强制合并status: 'pending'后交给useMutationState,再取length(见 packages/vue-query/src/useMutationState.ts)。

二、Options 完整说明

官方文档 docs/framework/vue/reference/useMutationState.md 给出的选项如下,下面补充类型与默认行为:

选项类型说明
options.filtersMutationFilters(可选)用于缩小要匹配的 mutation 范围,详见第三节。注意这里支持MaybeRefDeep,即可以直接传ref/ 响应式对象,也可以传 getter 函数。
options.select(mutation: Mutation) => TResult(可选)对每个匹配到的 mutation 进行状态转换,返回的值组成结果数组。省略时返回mutation.state。
queryClientQueryClient(可选)传入自定义 QueryClient;不传则使用最近上下文中的实例。

对应源码中的MutationStateOptions类型(packages/vue-query/src/useMutationState.ts):

export type MutationStateOptions<TResult = MutationState, ...> = { filters?: MutationFilters select?: (mutation: TMutation) => TResult }

此外,options本身既可以传对象,也可以传函数(() => MutationStateOptions)。从源码resolvedOptions的computed可以看出:传入函数时每次响应式依赖变化都会重新求值,配合cloneDeepUnref自动对filters内的ref解包。这一点在测试 packages/vue-query/src/tests/useMutationState.test.ts 中有直接验证:修改keyRef.value后,无需重新调用组合式函数,结果立即响应式更新。

返回值:Readonly<Ref<Array<TResult>>>—— 一个只读的 ref,其.value是select对每个匹配 mutation 处理后结果的数组。在模板中可直接访问mutationState(Vue 会自动解包),在<script setup>中则用mutationState.value。

三、MutationFilters:如何精确圈定目标 mutation

filters的类型即MutationFilters,完整字段来自 packages/query-core/src/utils.ts 与官方 Filters 指南 docs/framework/react/guides/filters.md(Vue 侧通过ref字段复用同一份内容):

  • mutationKey?: MutationKey:按 mutation key 匹配。不传exact时是"前缀包含"式匹配(partial match),即传入['posts']会匹配['posts', 1]等所有以该 key 开头的 mutation。
  • exact?: boolean:true时要求 key完全相等才匹配。
  • status?: MutationStatus:按状态过滤。MutationStatus定义为'idle' | 'pending' | 'success' | 'error'(见 packages/query-core/src/types.ts)。
  • predicate?: (mutation: Mutation) => boolean:最终过滤器,对每个候选 mutation 求值;可用它实现按variables、data等任意条件的细粒度筛选。

对应matchMutation的核心匹配逻辑(packages/query-core/src/utils.ts):

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 } } // ... 后续再按 status、predicate 过滤 }

注意区分:MutationCache.find(单查)默认exact: true,而findAll(供useMutationState使用)默认exact: false,即默认按 key 前缀模糊匹配。如果只想命中某个完全相同的 key,记得显式加exact: true。

四、三个官方示例:从取 variables 到取"最新一次"结果

示例 1:获取所有进行中 mutation 的 variables

import { useMutationState } from '@tanstack/vue-query' const variables = useMutationState({ filters: { status: 'pending' }, select: (mutation) => mutation.state.variables, })

这里variables.value是当前所有pendingmutation 的variables数组,非常适合在列表页展示"N 个请求提交中"。

示例 2:通过 mutationKey 获取特定 mutation 的 data

import { useMutation, useMutationState } from '@tanstack/vue-query' const mutationKey = ['posts'] // 需要与该 mutation 的 key 保持一致 const mutation = useMutation({ mutationKey, mutationFn: (newPost) => { return axios.post('/posts', newPost) }, }) const data = useMutationState({ filters: { mutationKey }, select: (mutation) => mutation.state.data, })

data.value中是所有 key 以['posts']开头的 mutation 的成功数据。若一个 mutation 执行了多次,数组会包含多次记录(见示例 3 的说明)。

示例 3:访问同一 mutationKey 下"最新一次"调用的数据

mutate每调用一次,就会向 mutation cache 中新增一条记录,并在gcTime毫秒后才被清理(默认 gcTime 见QueryClient默认配置)。因此useMutationState返回的数组是按调用顺序追加的,最新一次永远是最后一个元素:

import { useMutation, useMutationState } from '@tanstack/vue-query' const mutationKey = ['posts'] const mutation = useMutation({ mutationKey, mutationFn: (newPost) => { return axios.post('/posts', newPost) }, }) const data = useMutationState({ filters: { mutationKey }, select: (mutation) => mutation.state.data, }) // 最新一次 mutation 的数据 const latest = data.value[data.value.length - 1]

结合 packages/query-core/src/mutationCache.ts 可知:MutationCache内部用Set保存全部 mutation,useMutation的每次调用都会经build()产生一个新实例并add()进缓存,随后触发added通知——这正是useMutationState数组会"越变越长"、需要取[length - 1]的根本原因。缓存清理则由gcTime(默认 5 分钟,可在QueryClient或QueryCache级别覆盖)控制。

五、进阶用法

1. 用 getter 让 filters 响应式

当筛选条件本身依赖其它响应式状态时,直接传函数即可,无需手动 watch:

import { ref } from 'vue' import { useMutationState } from '@tanstack/vue-query' const filterStatus = ref<'idle' | 'pending' | 'success' | 'error'>('pending') const states = useMutationState(() => ({ filters: { status: filterStatus.value }, select: (mutation) => mutation.state, }))

2. 用 predicate 做任意条件筛选

例如只看"某个 userId 的 mutation":

const states = useMutationState({ filters: { predicate: (mutation) => mutation.state.variables?.userId === currentUserId, }, select: (mutation) => mutation.state, })

3. 结合 select 提取结构化数据

select可以返回任意结构,常见做法是返回一个可观察对象,配合 Vue 模板或computed聚合统计:

const summary = useMutationState({ filters: { status: 'pending' }, select: (mutation) => ({ id: mutation.options.mutationKey, variables: mutation.state.variables, error: mutation.state.error, }), })

4. 自定义 QueryClient

在测试或多客户端场景下可显式传入:

import { useQueryClient, useMutationState } from '@tanstack/vue-query' const client = useQueryClient() const states = useMutationState({ filters: { status: 'pending' } }, client)

六、测试佐证:行为即契约

仓库测试 packages/vue-query/src/tests/useMutationState.test.ts 覆盖了三个关键行为,可作为使用时的契约依据:

  1. 按 key + status 筛选:调用mutate(variables)后,useMutationState({ filters: { mutationKey: key, status: 'pending' }, select: (m) => m.state.variables })的.value等于[variables](第 19-36 行)。
  2. 无 filters 时返回全部:不带任何参数时,mutationState.value[0]?.variables即最近一次 mutation 的 variables(第 38-54 行)。
  3. getter 选项的响应式:修改ref驱动的 filters 后,结果自动更新(第 56-80 行)。

七、使用注意与常见陷阱

  • 必须在 setup 作用域内调用:useMutationState依赖onScopeDispose清理订阅。在<script setup>、setup()或 effect scope 中调用是安全的;在普通函数/事件回调中调用会漏掉清理逻辑,长期累积可能造成内存泄漏。
  • 返回数组随调用次数增长:同 key 的 mutation 每mutate一次就会多一条记录(在gcTime内)。取"最新"务必用[length - 1],不要假设数组长度恒为 1。
  • 默认是模糊匹配 key:filters.mutationKey不传exact: true时按前缀匹配,可能"误伤"同前缀的其它 mutation。
  • 只读返回:结果是Readonly<Ref<...>>,不能通过修改它反向影响 mutation 状态;要控制 mutation 请使用useMutation的mutate/mutateAsync。
  • 与useIsMutating的关系:useIsMutating可视为useMutationState的pending计数封装;如果你只需要"有几个在跑",用 useIsMutating 更轻量。

结语

useMutationState是 Vue Query 中少有的"面向全局 mutation 状态"的观测入口:一个组合式函数,即可完成"筛选 → 转换 → 响应式订阅"全流程。理解它之后,你不仅能优雅地实现"多 mutation 全局进度条""批量提交状态聚合""追踪最新一次变更结果"等场景,也能借此一窥MutationCache事件驱动的架构设计——这也是它区别于useMutation的核心价值所在。

【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询