news 2026/9/10 14:14:14

TanStack Query Preact 适配层的 `UseMutateAsyncFunction` 类型:理解 `mutateAsync` 的返回类型与用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Query Preact 适配层的 `UseMutateAsyncFunction` 类型:理解 `mutateAsync` 的返回类型与用法

TanStack Query Preact 适配层的UseMutateAsyncFunction类型:理解mutateAsync的返回类型与用法

【免费下载链接】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

本文围绕 TanStack Query 在 Preact 框架适配层(@tanstack/preact-query)公开的类型别名UseMutateAsyncFunction展开,讲解它在 useMutation 返回结果中的位置、四个泛型参数的含义、与UseMutateFunction(即mutate)的区别,以及为何返回 Promise 的行为会成为"并发提交表单、乐观更新回滚、等待全部 mutation 完成"等场景的类型基础。读完你既能按类型签名精确写出类型安全的 mutation 调用,也能理解mutateAsync从 Preact Hook 到 query-core 底层Mutation.execute的完整调用链。

UseMutateAsyncFunction类型别名速览

在 type-aliases 文档目录中,该类型的定义只有一行:

type UseMutateAsyncFunction<TData, TError, TVariables, TOnMutateResult> = MutateFunction<TData, TError, TVariables, TOnMutateResult>;

也就是说,它并不是一个全新的函数形态,而是把@tanstack/query-core中的MutateFunction原样再导出一次,并改名为带有Use前缀的框架层类型,用来表达"这是由useMutation返回的mutateAsync"这一语义。其源码位置在 packages/preact-query/src/types.ts:453,紧随 UseMutateFunction 定义 之后,注释明确写道:"The type ofmutateAsync, as returned byuseMutation. Similar toUseMutateFunction, but returns a promise which can be awaited."(这是useMutation返回的mutateAsync的类型,与UseMutateFunction类似,但返回一个可以被await的 Promise。)

与相邻类型的关系

在 packages/preact-query/src/types.ts 中,这一族类型构成完整的层级:

  • UseMutateFunction<TData, TError, TVariables, TOnMutateResult>(第 432 行):mutate的类型,返回void,是"发射后不管"(fire-and-forget)的形态,错误通过 mutation 状态暴露,而不是抛出。
  • UseMutateAsyncFunction<...>(第 453 行):mutateAsync的类型,返回Promise<TData>
  • UseBaseMutationResult<...>(第 470 行):使用Override把 query-core 的MutationObserverResult中的mutate收窄为框架层的UseMutateFunction,并追加mutateAsync: UseMutateAsyncFunction<...>字段(第 482-487 行)。
  • UseMutationResult<...>(第 499 行):useMutation的最终返回值,等价于UseBaseMutationResult

因此在使用useMutation返回值时,编译器能同时给出两种调用形态的完整签名:mutate形如(...args) => voidmutateAsync形如(...args) => Promise<TData>

四个泛型参数逐一拆解

UseMutationOptionsMutateFunction保持完全一致的顺序,UseMutateAsyncFunction携带四个类型参数:

类型参数默认值含义
TDataunknownmutation 函数(mutationFn)成功 resolve 出来的数据类型。
TErrorDefaultErrormutation 函数可能抛出的错误类型,query-core 中默认取DefaultError(通常是Error)。
TVariablesvoid调用mutateAsync时需要传入 mutation 函数的变量类型。
TOnMutateResultunknownonMutate返回值的类型,它会被传给onSuccess/onError/onSettled作为context(文档中称onMutateResult)参数——典型用途是承载乐观更新时保存的"回滚数据"。

这四个参数在 query-core 的 MutateFunction 定义 中共享同名语义;而 preact 的 UseMutationOptions 又把MutationObserverOptions(本身继承 MutationOptions)中的_defaulted内部字段剔除后原样暴露,因此四个泛型在"选项"与"结果函数"之间是严格一一对应、可以自动推导的。

关于TVariables与参数可选性

mutateAsync的第一个形参是否为必填,取决于TVariables是否包含undefined。这一定义来自 query-core 的辅助类型MutateFunctionRest(packages/query-core/src/types.ts:1179):

export type MutateFunctionRest<TData, TError, TVariables, TOnMutateResult> = undefined extends TVariables ? [variables?: TVariables, options?: MutateOptions<...>] : [variables: TVariables, options?: MutateOptions<...>]

TVariables缺省为void(而undefined可赋值给void)时,变量参数是可选的,即mutateAsync()可以不带参数调用;当TVariables被推导为如string{ id: number }这类具体类型时,第一个参数变为必填。第二个参数始终可选,它对应 query-core 中 MutateOptions 结构——允许你在单次调用时传入局部的onSuccess/onError/onSettled回调。

mutateAsyncmutate:两种调用形态的本质区别

mutation 概念区分查询与写入:与查询不同,mutation 通常用于"创建/更新/删除数据或产生服务端副作用",这是 useMutation.ts 顶部注释对useMutation的定位说明。mutatemutateAsync表面上功能相同,差异集中在错误处理与并发控制上:

  1. 错误传播方式不同mutate是 fire-and-forget 的:返回void,错误通过mutation.error状态、status或挂在 mutation 上的onError呈现,不会抛出到事件处理函数之外。而mutateAsync返回Promise<TData>,mutation 失败时 Promise 会 reject,因此必须配合try/catchcatch链或Promise.all处理。这正是源码注释 "errors are surfaced through the mutation result, not thrown"(错误通过 mutation 结果暴露而非抛出)描述的对比。
  2. 并发多次调用的归属不同。Hook 级回调会为每次 mutation 触发,但"单次调用传入的回调"只对最后一次调用生效。文档注释指出(useMutation.ts:128-129):"Callbacks passed per call tomutateonly fire for the last call —mutateAsyncgives you a promise per call instead"。即:想要"一次性提交多条 todo 并分别等待"的场景,必须依赖mutateAsync逐调用返回的 Promise,Promise.all/Promise.allSettled才能拿到每一次调用的结果。
  3. 返回值可用于流程编排mutateAsync的成功值(TData)可以在.thenawait之后继续参与后续逻辑,而mutate拿不到返回值。

源码层面的差异位于 useMutation.ts:224-244:

const mutate = useCallback<UseMutateFunction<TData, TError, TVariables, TOnMutateResult>>( (...args) => { observer.mutate(args[0] as TVariables, args[1]).catch(noop) // 错误被吞掉 }, [observer], ) ... return { ...result, mutate, mutateAsync: result.mutate }

注意最后一行:框架层没有单独实现mutateAsync,它直接复用了MutationObserver.mutate并原样挂到返回值上,而类型上则同时收窄了mutate、补充了mutateAsync签名。因此 Promise 语义的源头在 query-core:MutationObserver.mutate(packages/query-core/src/mutationObserver.ts:136-151)先记录本次调用的#mutateOptions、从MutationCache构建 mutation,然后直接return this.#currentMutation.execute(variables)——把内部执行 Promise 透传出来。

底层调用链:从 Promise 到 mutation 执行

MutateFunction的签名是(...rest) => Promise<TData>,这个 Promise 的完整生命周期可以沿 query-core 追踪到 Mutation.execute:

  1. 构造MutationFunctionContext(含clientmetamutationKey);
  2. 通过createRetryer包装实际mutationFn(variables, mutationFnContext),并应用retryretryDelaynetworkMode配置;
  3. 进入pending状态后依次触发:mutationCache.config.onMutate→ 选项级options.onMutate,其返回值成为context(即TOnMutateResult);
  4. 等待retryer.start()resolve 后,依次触发 cache 级与选项级的onSuccessonSettled,随后#dispatch({ type: 'success', data })return data
  5. 任一环节抛错则进入catch分支,触发onErroronSettled,最终把错误作为 rejection 抛出。

因此await mutation.mutateAsync(...)得到的TData,正是mutationFn的 resolve 值;onMutate返回的 context 会在整个 Promise 生命周期内作为第 3 个参数传给onError等回调,用于乐观更新回滚。

实战用法示例

场景一:等待全部 mutation 完成(批量提交)

当你想"点一次按钮同时新增多条数据,并等待它们全部落地"时,per-call 回调只对最后一次mutate生效,正确的做法是用mutateAsync+Promise.all(示例源自 useMutation.ts 文档注释):

import { useMutation, useQueryClient } from '@tanstack/preact-query' function AddTodos() { const queryClient = useQueryClient() const addMutation = useMutation({ mutationFn: addTodo, onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }), }) async function handleAddAll(todos: Array<string>) { try { await Promise.all(todos.map((todo) => addMutation.mutateAsync(todo))) } catch (error) { console.error('Failed to add todos:', error) } } return ( <button onClick={() => handleAddAll(['Todo 1', 'Todo 2', 'Todo 3'])}> Add all </button> ) }

如果这些子 mutation 可能各自独立失败、而你又想知道"具体是哪几条失败",则换用Promise.allSettled逐个检查addResult.status === 'rejected'(完整示例见 useMutation.ts:172-190)。

场景二:乐观更新回滚

TOnMutateResult最常见的承载对象就是回滚数据。在onMutate中先cancelQueries并保存旧数据,失败时用其恢复(见 useMutation.ts:100-119 的 JSDoc 示例,以及 乐观更新指南):

const addMutation = useMutation({ mutationFn: addTodo, onMutate: async (newTodo) => { await queryClient.cancelQueries({ queryKey: ['todos'] }) const previousTodos = queryClient.getQueryData<Array<string>>(['todos']) queryClient.setQueryData<Array<string>>(['todos'], (old) => [ ...(old ?? []), newTodo, ]) // 传给 onError 作为 context:类型上正是 TOnMutateResult return { previousTodos } }, onError: (_err, _newTodo, context) => { queryClient.setQueryData(['todos'], context?.previousTodos) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) }, })

此时useMutation的四个泛型可被自动推导:TData来自addTodo的返回类型、TVariables来自其入参、TOnMutateResult来自onMutate的返回对象形状,mutateAsync的参数与返回类型都会随之收紧,await之后拿到的数据类型精确无误。

类型层面的验证与推导

框架自带类型测试可以印证UseMutateAsyncFunction的行为约定,见 packages/preact-query/src/tests/useMutation.test-d.tsx:

it('should type mutateAsync with correct return type', () => { // mutation.mutateAsync 可调用、返回 Promise<TData> expectTypeOf(mutation.mutateAsync).toBeCallableWith('test') expectTypeOf(mutation.mutateAsync('test')).toEqualTypeOf<Promise<number>>() }) // TVariables 为 void 时,mutateAsync 可不带参数调用 expectTypeOf(mutation.mutateAsync).toBeCallableWith()

这与前文MutateFunctionRest的可选参数规则互相印证:当TVariables不含undefined时必须传参,返回类型恒为Promise<TData>

进一步,若希望把 mutation 的选项集中管理并在useMutation之外复用,可借助 mutationOptions(实现见 packages/preact-query/src/mutationOptions.ts),它与useMutation接受同一组UseMutationOptions,便于通过mutationKey配合 useMutationState 查询 mutation 状态。

小结

  • UseMutateAsyncFunction<TData, TError, TVariables, TOnMutateResult>本质是 query-coreMutateFunction的框架层再导出,描述useMutation返回的mutateAsync,返回类型恒为Promise<TData>
  • 它与返回voidUseMutateFunctionmutate)构成互补:前者可用于await/Promise.all编排与错误捕获,后者适合事件回调里"发射后不管"的写法;
  • 四个泛型参数与UseMutationOptions一一对应,TVariables是否包含undefined决定变量参数是否必填;
  • Promise 语义的底层实现在 MutationObserver.mutate 与 Mutation.execute,框架层在 useMutation.ts:244 直接复用result.mutate作为mutateAsync

理解了这一类型别名,你就掌握了 TanStack Query mutation API 中"可等待调用"一侧的完整类型契约:无论是批量写入、逐步上传、乐观回滚还是提交后跳转导航(可结合 mutations 指南与 invalidations-from-mutations 指南进一步阅读),写出的代码都能获得端到端的类型安全。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 14:14:08

基于Matlab的配电网源-荷-储协同优化调度实践

1. 项目概述&#xff1a;源-荷-储协同的配电网优化调度在电力系统领域&#xff0c;随着分布式能源渗透率不断提高&#xff0c;传统配电网正面临前所未有的运行挑战。我最近完成的这个IEEE33节点配电网优化调度项目&#xff0c;核心目标是通过Matlab实现"源-荷-储"三者…

作者头像 李华
网站建设 2026/9/10 14:13:57

LabVIEW视觉一键尺寸测量系统开发与应用

1. LabVIEW视觉一键尺寸测量仪概述在工业自动化检测领域&#xff0c;尺寸测量一直是核心需求之一。传统卡尺、千分尺等接触式测量方式效率低下且容易引入人为误差。我们团队基于LabVIEW开发的视觉一键尺寸测量系统&#xff0c;通过工业相机图像处理算法的方式&#xff0c;实现了…

作者头像 李华
网站建设 2026/9/10 14:13:46

COSCon‘25开源协同论坛:产学研开源转化实战解析

1. 项目概述&#xff1a;COSCon25产研开源协同论坛的核心价值2025年开源社年度峰会&#xff08;COSCon25&#xff09;即将拉开帷幕&#xff0c;其中最受关注的产研开源协同论坛议程近日正式发布。作为连续参与三届开源峰会的技术布道师&#xff0c;我深刻感受到这个论坛正在打破…

作者头像 李华