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.filters | MutationFilters(可选) | 用于缩小要匹配的 mutation 范围,详见第三节。注意这里支持MaybeRefDeep,即可以直接传ref/ 响应式对象,也可以传 getter 函数。 |
options.select | (mutation: Mutation) => TResult(可选) | 对每个匹配到的 mutation 进行状态转换,返回的值组成结果数组。省略时返回mutation.state。 |
queryClient | QueryClient(可选) | 传入自定义 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 覆盖了三个关键行为,可作为使用时的契约依据:
- 按 key + status 筛选:调用
mutate(variables)后,useMutationState({ filters: { mutationKey: key, status: 'pending' }, select: (m) => m.state.variables })的.value等于[variables](第 19-36 行)。 - 无 filters 时返回全部:不带任何参数时,
mutationState.value[0]?.variables即最近一次 mutation 的 variables(第 38-54 行)。 - 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),仅供参考