
tRPC v11 useMutation 实战指南在 trpc/react-query 中发起端到端类型安全的写操作【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc本指南围绕 useMutation.md 展开系统讲解如何在 React 应用中使用trpc/react-query暴露的useMutation()Hook 调用 tRPC 后端定义好的 mutation 过程procedure。它教你完成从服务端 mutation 路由的定义、客户端类型化 Hook 的生成到用mutate/mutateAsync触发请求、读取isPending/error/data状态、以及成功回调的完整闭环。读完本指南你将掌握在登录、表单提交、增删改等写操作场景中落地“端到端类型安全”的标准姿势并理解其底层的 mutation key 与回调包装机制。trpc/react-query中的这些 Hook 本质上是tanstack/react-queryv5之上的一层薄封装——它替你接好了类型系统与网络调用而选项、状态机与缓存语义则完整继承自 TanStack Query。因此下文在讲解过程中会同时交代两者的边界方便你在需要时无缝阅读更底层的行为细节。前置准备把 tRPC 客户端接入 React要使用useMutation()请先确保项目已经完成 setup.mdx 中的接入步骤安装trpc/client、trpc/react-query、tanstack/react-query用createTRPCReactAppRouter()创建类型化的trpc对象并通过trpc.Provider与QueryClientProvider包裹应用。创建类型化客户端的标准写法如下// utils/trpc.ts import { createTRPCReact } from trpc/react-query; import type { AppRouter } from ../server; export const trpc createTRPCReactAppRouter();createTRPCReact接收路由器类型作为泛型参数在 createTRPCReact.tsx 中通过DecorateRouterRecord/DecorateProcedure把路由树中每个过程procedure“装饰”成带useQuery、useMutation等 Hook 的代理对象。也就是说只要后端appRouter里的类型是正确的trpc.login.useMutation()之类的调用就会被自动类型化无需手工编写任何接口请求层代码。第一步在服务端定义一个 mutation 过程useMutation()的目标是后端通过.mutation()声明的过程。在 tRPC 中创建 mutation 的语法与创建 query 几乎一致——区别仅在于把.query()换成.mutation()并使用.input()配合 zod或其他验证器校验并推断入参。// server/routers/_app.ts import { initTRPC } from trpc/server; import { z } from zod; export const t initTRPC.create(); export const appRouter t.router({ // 在 login 路径上创建过程 // 声明语法与创建 query 完全相同 login: t.procedure // 使用 zod schema 校验并推断输入值 .input( z.object({ name: z.string(), }), ) .mutation((opts) { // 在这里执行真正的登录逻辑 return { user: { name: opts.input.name, role: ADMIN as const, }, }; }), }); export type AppRouter typeof appRouter;要点说明opts.input的类型由.input()中的 zod schema 自动推断在回调体内已是非undefined的、通过校验的值.mutation()的返回值即客户端data中拿到的响应整个链路输入输出全部有静态类型保证appRouter的类型会被导出并喂给客户端的createTRPCReactAppRouter()这是“端到端类型安全”的起点。第二步在组件中用 useMutation() 发起写操作拿到类型化的trpc后在任意组件内即可通过trpc.路由路径.useMutation()获得对应 mutation 的封装 Hook。以下是一个完整可运行的登录表单组件// components/MyComponent.tsx import React from react; import { trpc } from ../utils/trpc; export function MyComponent() { const mutation trpc.login.useMutation(); const handleLogin () { const name John Doe; mutation.mutate({ name }); }; return ( div h1Login Form/h1 button onClick{handleLogin} disabled{mutation.isPending} Login /button {mutation.error pSomething went wrong! {mutation.error.message}/p} /div ); }运行流程点击按钮 →mutation.mutate({ name })入参对象必须符合服务端 zod schema{ name: string }否则编译期直接报错请求进行期间mutation.isPending true按钮被禁用失败时mutation.error非空其类型为TRPCClientErrorLikeAppRouter包含经 tRPC 标准化后的错误信息message等成功时mutation.data即为后端.mutation()返回的对象此处为{ user: { name, role } }。mutate 与 mutateAsync同步触发与异步等待useMutation()的返回值提供两个触发方法语义来自 TanStack Query但入参、返回数据类型均由 tRPC 注入mutation.mutate(variables)立即发起请求并返回void。适合事件回调场景如onClick配合onSuccess/onError/onSettled回调处理结果。mutation.mutateAsync(variables)返回一个 Promiseresolve 后可直接拿到data。适合需要await结果的逻辑如表单校验后串行提交也便于与try/catch或useEffect组合const handleSubmit async () { try { const data await mutation.mutateAsync({ name: John Doe }); console.log(登录成功用户角色, data.user.role); } catch (err) { console.error(登录失败, err); } };返回值字段速查表trpc.login.useMutation()的返回值是TRPCHookResult UseMutationResultTOutput, TError, TInput, TContext见 hooks/types.ts#L305-L314。除 tRPC 附加的trpc.path外其余字段均与 TanStack Query v5 的UseMutationResult一致字段类型/取值含义mutate/mutateAsync(variables) void / PromiseTOutput触发写操作dataTOutput \| undefined成功后后端返回的数据errorTError \| null失败时的错误对象isPendingboolean请求是否进行中v5 中替代旧版isLoadingisSuccessboolean最近一次是否成功isErrorboolean最近一次是否失败statusidle \| pending \| success \| error生命周期状态variablesTInput \| undefined最近一次提交的入参reset() void将状态重置为初始idle态清空data/errortrpc.pathstring本次调用对应的过程路径如login由 TRPCHookResult 注入注意v5 起mutate抛出的错误属于“未被捕获的 Promise 拒绝”更推荐使用回调或mutateAsync捕获错误isPending是 v5 中判断“进行中”的推荐字段原文档示例即用它禁用按钮。常用配置选项onSuccess、onError 与乐观更新useMutation()可接收一个可选的配置对象其类型为UseTRPCMutationOptionsTInput, TError, TOutput, TContext该类型在 hooks/types.ts#L154-L160 中定义为继承 TanStack Query 的UseMutationOptionsTOutput, TError, TInput, TContext即返回值类型是后端输出错误类型是TRPCClientErrorLike变量类型是过程入参——因此所有回调的入参都会被自动类型化配置对象整体也处于编译期保护之下。const mutation trpc.addPost.useMutation({ // 请求发起前同步执行常用于乐观更新 onMutate: async (variables) { console.log(准备新增文章, variables.title); }, // 成功后回调可在此失效化相关查询见下文 onSuccess: (data, variables, context) { console.log(新增成功新 id 为, data.id); }, onError: (error, variables, context) { console.error(失败, error.message); }, onSettled: (data, error, variables, context) { console.log(无论成败都会执行); }, // 失败自动重试次数写操作默认不重试 retry: false, });泛型参数TContext专为乐观更新设计在onMutate中返回上下文对象如被修改前的旧数据该对象会原样传递到onError/onSuccess/onSettled的context参数中配合queryClient.cancelQueries、setQueryData可实现回滚具体模式与 useQuery.md 中的缓存约定保持一致。成功后的数据同步失效化相关查询服务端数据被 mutation 改变后最常见的诉求是让依赖它的 query 重新拉取。tRPC 提供useUtils()旧版名为useContext已被标记弃用见 createTRPCReact.tsx获取一组工具方法其中invalidate()可按过程路径定向失效import { trpc } from ../utils/trpc; function PostCreator() { const utils trpc.useUtils(); const addPost trpc.addPost.useMutation({ onSuccess: () { // 新增完成后让 post.list 查询自动重新拉取 utils.post.list.invalidate(); }, }); return ( button onClick{() addPost.mutate({ title: Hello })} {addPost.isPending ? 提交中… : 新增文章} /button ); }完整的失效化、预取与缓存读写方法族可参阅 useUtils.mdx。源码视角useMutation 到底包装了什么从 createHooksInternal.tsx#L320-L358 的实现可以看到useMutation做了四件关键的事构造 mutation keygetMutationKeyInternal(path)把 tRPC 的过程路径如login转成一个确定性的查询键并透传给 TanStack 的useMutation作为mutationKey合并默认配置queryClient.defaultMutationOptions(queryClient.getMutationDefaults(mutationKey))因此可以用queryClient.setMutationDefaults按路径预设默认回调测试见 mutationkey.test.tsx、queryClientDefaults.test.tsx注入网络调用把真正的 RPC 请求封装成 TanStack 的mutationFn内部调用底层客户端client.mutation(...getClientArgs([path, { input }], opts))从而复用 tRPC client 的链接links体系包装 onSuccess 并暴露路径success 回调会经mutationSuccessOverride处理见createRootHooks中config?.overrides?.useMutation?.onSuccess ?? ((options) options.originalFn())并把{ trpc: { path } }追加到 Hook 返回值上。// 经过简化的内部实现示意源自 createHooksInternal.tsx 的 useMutation const mutationKey getMutationKeyInternal(path); // ① 由过程路径生成 key const defaultOpts queryClient.defaultMutationOptions( queryClient.getMutationDefaults(mutationKey), // ② 合并 setMutationDefaults ); const hook __useMutation( { ...opts, mutationKey, mutationFn: (input) client.mutation(...getClientArgs([path, { input }], opts)), // ③ 走 tRPC client onSuccess(...args) { const originalFn () opts?.onSuccess?.(...args) ?? defaultOpts?.onSuccess?.(...args); return mutationSuccessOverride({ originalFn, queryClient, meta: /* 合并后的 meta */ }); }, }, queryClient, );与此同时类型层的接入点位于 createTRPCReact.tsx#L353-L370DecoratedMutation声明了每个 mutation 过程都具备的useMutationTContext unknown(opts?)签名并用TDef[input]、TDef[output]、TRPCClientErrorLikeTDef精确约束入参、返回数据与错误类型DecorateProcedure则保证只有后端声明为.mutation()的过程才会被装饰出useMutationquery 与 mutation 的能力不会互相串台。此外trpc/react-query还导出基于多态的mutationLikemutationLike.ts等类型工具允许在声明式组件中把“某个带useMutation的过程”作为普通 props 传入并保持类型不丢失相关行为在 polymorphism.test.tsx 中有大量覆盖含mutateAsync、isPending的端到端验证。进阶用 createTRPCReact 的 overrides 做全局 onSuccess当多个 mutation 都希望复用同一份成功处理逻辑例如“成功后统一失效所有查询”时不必在每个组件里重复写回调可以在创建 tRPC React 对象时通过overrides.useMutation.onSuccess注入全局拦截器const trpc createTRPCReactAppRouter({ overrides: { useMutation: { async onSuccess(opts) { // opts.originalFn() 会继续调用组件内或 mutation defaults 里定义的 onSuccess if (!opts.meta[skipInvalidate]) { await opts.originalFn(); await opts.queryClient.invalidateQueries(); } }, }, }, });回调收到的opts对象包含originalFn、queryClient与meta三要素meta为调用方传入的meta与默认meta的合并结果典型落地用法可参考仓库测试 overrides.test.tsx —— 它演示了“每次 mutation 成功后清空查询缓存”的全局策略也可按meta标记如skipInvalidate跳过特定调用。小结与常见注意事项useMutation是从“类型化声明”到“写操作执行”的关键一环。实践中的几条经验写过程用.mutation()读过程用.query()不要混用mutation 默认不做自动重试、不进入 SSR 预取这与它的写语义相匹配判断进行中状态优先使用isPendingv5 语义而不是旧版习惯的isLoading依赖事件回调拿结果优先mutate需要在异步流程中串联后续逻辑时用mutateAsyncmutation key 由过程路径推导所以用queryClient.setMutationDefaults或全局overrides时只要路径一致即可命中——这正是 tRPC 薄封装设计想要表达的类型与路由由 tRPC 保证缓存与状态机交给 TanStack Query两者各司其职。如果你还想了解 query 侧useQuery/useInfiniteQuery/useQueries的完整用法或服务端查询工具集useUtils可继续阅读 client/react 目录 下的对应文档所有 Hook 的实现与测试均可在仓库packages/react-query/中查证。【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考