ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

tRPC React Query 类型推断完全指南:从 `inferRouterInputs` 到 `RouterLike` / `UtilsLike`(v10 版)

tRPC React Query 类型推断完全指南:从 `inferRouterInputs` 到 `RouterLike` / `UtilsLike`(v10 版) tRPC React Query 类型推断完全指南从inferRouterInputs到RouterLike/UtilsLikev10 版【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc本指南以 www/versioned_docs/version-10.x/client/react/infer-types.md 为骨架结合trpc/react-query的源码实现与测试用例展开面向 tRPC v10 React Query 的开发者。你将掌握两件事一是如何用inferReactQueryProcedureOptions让自定义 Hook 的 options 参数与路由全链路类型对齐二是如何借助trpc/react-query/shared导出的RouterLike/UtilsLike抽象类型把“router 工厂”生成的同构路由下发给多个前端组件复用彻底告别手工维护类型。1. 先厘清类型推断的两层能力tRPC 的类型安全来自一套“一次定义、处处推导”的机制。服务端用t.router({...})定义好appRouter并导出export type AppRouter typeof appRouter客户端所有 hook、调用、错误处理都围绕这一单一类型来源展开。在 React 集成里类型推断其实分为两层路由层跨客户端通用trpc/server导出的inferRouterInputsTRouter与inferRouterOutputsTRouter可把AppRouter中各 procedure 的入参input与出参output映射成一个可索引的深层对象类型。这部分在 www/versioned_docs/version-10.x/client/vanilla/infer-types.md 中有完整讲解底层实现位于 packages/server/src/unstable-core-do-not-import/clientish/inference.ts。React Query 层本主题trpc/react-query集成在路由层之上再提供专属的推断辅助类型专门服务useQuery/useMutation/useUtils等 React 场景——这正是本文的核心。两者的分工可以概括为inferRouterInputs回答“过程需要什么输入、返回什么输出”而inferReactQueryProcedureOptions回答“某个 procedure 的 React Query options如enabled、retry、onSuccess长得什么样”。版本说明本文对应 tRPC v10 文档目录。v10 的 React 集成包名为trpc/react-query对应本仓库 packages/react-query仓库中同时存在的packages/tanstack-react-query是 v11 面向新版 TanStack React Query 的集成二者概念相通但导入路径不同。2. 贯穿全文的示例 Router文档中的示例以一个post子路由为演示对象它包含三种典型 procedure无输入 query、带z.string()输入 query、带对象输入 mutation。// server.ts import { initTRPC } from trpc/server; import { z } from zod; const t initTRPC.create(); const appRouter t.router({ post: t.router({ list: t.procedure .query(() { // imaginary db call return [{ id: 1, title: tRPC is the best! }]; }), byId: t.procedure .input(z.string()) .query(({ input }) { // imaginary db call return { id: 1, title: tRPC is the best! }; }), create: t.procedure .input(z.object({ title: z.string(), text: z.string(), })) .mutation(({ input }) { // imaginary db call return { id: 1, ...input }; }), }), }); export type AppRouter typeof appRouter;这里list无输入、byId以z.string()为输入、create以{ title, text }为输入。后续所有类型推断都建立在这个AppRouter上。3.inferReactQueryProcedureOptions让自定义 Hook 的 options 也拥有类型3.1 使用动机在创建围绕 tRPC procedure 的自定义 Hook如把usePostCreate、usePostById封装成业务层组件时通常需要透传useQuery/useMutation的配置。如果手写类型只要服务端入参或返回结构一变前端就会悄悄产生类型漂移。正确做法是直接从 router 推导。trpc/react-query为此导出了inferReactQueryProcedureOptions辅助类型。先在一个集中式trpc.ts文件里一次性声明// trpc.ts import { createTRPCReact, type inferReactQueryProcedureOptions, } from trpc/react-query; import type { inferRouterInputs, inferRouterOutputs } from trpc/server; import type { AppRouter } from ./server; // infer the types for your router export type ReactQueryOptions inferReactQueryProcedureOptionsAppRouter; export type RouterInputs inferRouterInputsAppRouter; export type RouterOutputs inferRouterOutputsAppRouter; export const trpc createTRPCReactAppRouter();ReactQueryOptions按“过程路径”组织的 React Query options 类型RouterInputs/RouterOutputs来自trpc/server的通用推断供需要手写输入/输出类型的场景使用trpc类型化的 React hooks 客户端createTRPCReactAppRouter()可参考 www/versioned_docs/version-10.x/client/react/setup.mdx。3.2 封装带 options 透传的 mutation Hook以“创建 post 后失效整个 post 路由缓存”为例。usePostCreate接收可选的PostCreateOptions即ReactQueryOptions[post][create]内部用展开运算符透传所有用户 options再叠加一段固定逻辑——在onSuccess里调用utils.post.invalidate()使post路由下的所有查询失效同时仍然调用用户自己的onSuccess// usePostCreate.ts import { trpc, type ReactQueryOptions, type RouterInputs, type RouterOutputs, } from ./trpc; type PostCreateOptions ReactQueryOptions[post][create]; function usePostCreate(options?: PostCreateOptions) { const utils trpc.useUtils(); return trpc.post.create.useMutation({ ...options, onSuccess(post) { // invalidate all queries on the post router // when a new post is created utils.post.invalidate(); options?.onSuccess?.(post); }, }); }关键点在于...options展开后Hook 调用方传入的enabled、retry、onError等配置依然会被保留而onSuccess被重写为“先失效、再回调用户逻辑”的组合且post参数的类型自动是post.create的输出类型。3.3 封装 query Hook 并分别取 input / options 类型对 query 场景可把输入与 options 分开索引。PostByIdInput取自RouterInputs[post][byId]即stringPostByIdOptions取自ReactQueryOptions[post][byId]// usePostById.ts import { ReactQueryOptions, RouterInputs, trpc } from ./trpc; type PostByIdOptions ReactQueryOptions[post][byId]; type PostByIdInput RouterInputs[post][byId]; function usePostById(input: PostByIdInput, options?: PostByIdOptions) { return trpc.post.byId.useQuery(input, options); }封装完成后Hook 的调用方无需useQuery第一手接触trpc代理却仍能获得完整的服务端校验提示input传非字符串会直接报错返回数据也会被推断为{ id, title }。3.4 源码深挖这个 helper 到底做了什么从源码看inferReactQueryProcedureOptions是一个“沿 router 记录逐层映射”的类型级递归定义于 packages/react-query/src/utils/inferReactQueryProcedure.ts并经由 packages/react-query/src/index.ts 对外导出type inferReactQueryProcedureOptionsInner TRoot extends AnyRootTypes, TRecord extends RouterRecord, { [TKey in keyof TRecord]: TRecord[TKey] extends infer $Value ? $Value extends AnyQueryProcedure ? InferQueryOptionsTRoot, $Value : $Value extends AnyMutationProcedure ? InferMutationOptionsTRoot, $Value : $Value extends RouterRecord ? inferReactQueryProcedureOptionsInnerTRoot, $Value : never : never; }; export type inferReactQueryProcedureOptionsTRouter extends AnyRouter inferReactQueryProcedureOptionsInner TRouter[_def][_config][$types], TRouter[_def][record] ;可以拆解出三层逻辑递归结构保持对RouterRecord逐 key 映射。若当前值仍是一个嵌套路由RouterRecord就递归调用自身——这解释了为什么ReactQueryOptions[post][create]能按post.create的路径索引到深层 options。过程分类若当前值命中AnyQueryProcedure映射为InferQueryOptions命中AnyMutationProcedure则映射为InferMutationOptions两者皆非的普通值收敛为never防止脏类型进入结果。选项收窄同文件的InferQueryOptions通过Omit..., select | queryFn从UseTRPCQueryOptions中剥离了select与queryFn因为这些字段本就不应暴露给调用方数据默认类型取自inferTransformedProcedureOutput即经过 data transformer 处理后的输出类型错误类型统一绑定为TRPCClientErrorLikeTRoot。换言之ReactQueryOptions[post][byId]与trpc.post.byId.useQuery内部实际接受的 options 类型同源这正是“自定义 Hook 与内置 Hook 类型永不漂移”的保障。同样useMutation.test.tsx等测试也直接消费了该 helper 以校验配置类型packages/react-query/test/useMutation.test.tsx。4. Router Factory 与多态为“工厂函数”生成抽象类型4.1 适用场景与动机当应用中存在“多次创建结构相似的路由”的工厂函数典型如共享的 CSV 导出系统要挂到多个实体上、一个通用 CRUD 路由要适配多个数据源时很自然地希望不同实例之间共享同一套前端组件代码。问题在于trpc/react-query生成的 router 代理接口是高度具体的——两棵结构完全相同的路由在 TypeScript 的结构化类型系统下并不总是被视为互相兼容可参考 packages/react-query/test/polymorphism.test.tsx 开头的注释说明。于是trpc/react-query/shared导出若干**Like抽象类型用来生成“与某一路由接口形状兼容”的抽象视图从而可以把 router 作为 prop 传给通用 React 组件。4.2 第一步定义工厂并导出抽象类型下面的createMyRouter()由你自己实现唯一要求是随后能从它的返回值推导出t.router()的类型// api/factory.ts import { t, publicProcedure } from ./trpc; // trpc/react-query/shared exports several **Like types which can be used to generate abstract types import { RouterLike, UtilsLike } from trpc/react-query/shared; // Factory function written by you, however you need, // so long as you can infer the resulting type of t.router() later export function createMyRouter() { return t.router({ createThing: publicProcedure .input(ThingRequest) .output(Thing) .mutation(/* do work */), listThings: publicProcedure .input(ThingQuery) .output(ThingArray) .query(/* do work */), }) } // Infer the type of your router, and then generate the abstract types for use in the client type MyRouterType ReturnTypetypeof createMyRouter export MyRouterLike RouterLikeMyRouterType export MyRouterUtilsLike UtilsLikeMyRouterType文档原始片段中ThingRequest/Thing/ThingArray/ThingQuery为省略的 zod 结构export MyRouterLike亦应补上type关键字一份可运行、已定义好 zod schemaThingRequest z.object({ name })、Thing z.object({ id, name })、ThingQuery z.object({ filter })等的完整实现可参考 www/docs/client/react/infer-types.mdv11 文档已修订该示例。三个导出的要点MyRouterLike描述“与工厂返回路由形状兼容”的调用侧代理类型query/mutation 调用方式对应源码 packages/react-query/src/shared/polymorphism/routerLike.tsMyRouterUtilsLike描述对应的utils/上下文路径如invalidate、setData等对应源码 packages/react-query/src/shared/polymorphism/utilsLike.ts二者连同QueryLike/MutationLike由 packages/react-query/src/shared/polymorphism/index.ts 统一导出因此客户端可从trpc/react-query/shared子路径导入。4.3 第二步把抽象类型随AppRouter一起导出后端入口再把这些抽象类型转发给客户端// api/server.ts export type AppRouter typeof appRouter; // Export your MyRouter types to the client export type { MyRouterLike, MyRouterUtilsLike } from ./factory;这一步意味着客户端永远不直接依赖工厂内部的实现细节只依赖公开的抽象类型契约。4.4 第三步编写接收route与utils的通用组件前端组件以 props 接收符合抽象契约的route与utils内部就能像使用具体路由一样调用useQuery/useMutation并获得完整的输入输出校验// frontend/usePostCreate.ts import type { MyRouterLike, MyRouterUtilsLike, trpc, useUtils } from ./trpc; type MyGenericComponentProps { route: MyRouterLike; utils: MyRouterUtilsLike; }; function MyGenericComponent(props: MyGenericComponentProps) { const { route } props; const thing route.listThings.useQuery({ filter: qwerty, }); const mutation route.doThing.useMutation({ onSuccess() { props.utils.listThings.invalidate(); }, }); function handleClick() { mutation.mutate({ name: Thing 1, }); } return; /* ui */ } function MyPageComponent() { const utils useUtils(); return ( MyGenericComponent route{trpc.deep.route.things} utils{utils.deep.route.things} / ); } function MyOtherPageComponent() { const utils useUtils(); return ( MyGenericComponent route{trpc.different.things} utils{utils.different.things} / ); }这是本模式的核心收益同一个MyGenericComponent /只需传入不同深度/不同命名空间下的同构路由路径trpc.deep.route.things或trpc.different.things即可复用组件无需关心数据到底挂在哪个业务命名空间下。值得注意的细节在源码示例中一个真实的通用导出组件常常同时具备startmutation、listquery、statusquery等一组“同构过程”而工厂通过闭包注入不同的dataProvider实现对 issues、discussions、pull requests 等异构数据源共用同一套路由实现——见 packages/react-query/test/polymorphism.factory.tsx 中createExportRoute(baseProcedure, dataProvider)的写法。4.5 源码深挖RouterLike与UtilsLike的实现与测试佐证RouterLike的内部实现与inferReactQueryProcedureOptions高度同构——同样沿RouterRecord递归遇到 query 过程产出QueryLike、mutation 过程产出MutationLike、嵌套路由继续递归packages/react-query/src/shared/polymorphism/routerLike.tsexport type RouterLikeInner TRoot extends AnyRootTypes, TRecord extends RouterRecord, { [TKey in keyof TRecord]: TRecord[TKey] extends infer $Value ? $Value extends AnyQueryProcedure ? QueryLikeTRoot, $Value : $Value extends AnyMutationProcedure ? MutationLikeTRoot, $Value : $Value extends RouterRecord ? RouterLikeInnerTRoot, $Value : never : never; };而UtilsLike直接复用装饰后的 utils 代理记录packages/react-query/src/shared/polymorphism/utilsLike.tsexport type UtilsLikeTRouter extends AnyRouter DecoratedProcedureUtilsRecord TRouter[_def][_config][$types], TRouter[_def][record] ;QueryLike/MutationLike还额外导出了InferQueryLikeInput/InferQueryLikeData/InferMutationLikeInput/InferMutationLikeData等辅助推断类型见 packages/react-query/src/shared/polymorphism/queryLike.ts、packages/react-query/src/shared/polymorphism/mutationLike.ts可用来从抽象类型反推输入/数据形状。这套“多态”polymorphism机制在仓库中有完整的端到端测试佐证packages/react-query/test/polymorphism.test.tsx构造了github.issues、github.discussions、github.pullRequests等多个共享“文件导出”接口的路由并验证通用组件可被这些不同命名空间下的路由实例化使用packages/react-query/test/polymorphism.factory.tsx定义createExportRouteTBaseProcedure(baseProcedure, dataProvider)工厂与导出的抽象类型packages/react-query/test/polymorphism.common.tsx 与 packages/react-query/test/polymorphism.subtyped-factory.tsx分别提供共享根类型配置与“子类型化工厂”在基础工厂之上扩展实体子类型与额外 procedure。v10 文档末尾指向的完整示例原文档给出的是 GitHub 外链此处对应仓库内路径即为上述 packages/react-query/test/polymorphism.test.tsx 及其伴随工厂文件。5. 常见问题与最佳实践小结两个 helper 不要混淆inferReactQueryProcedureOptions处理的是“React Query hook 的 options 类型”面向自定义 Hook 封装inferRouterInputs/inferRouterOutputs处理的是“procedure 的输入/输出数据形状”跨框架通用。二者通常成对导出如trpc.ts中同时导出ReactQueryOptions、RouterInputs、RouterOutputs。统一收敛在一个trpc.ts把类型别名与trpc客户端放在同一模块再分发能避免各组件散落createTRPCReact重复实例化。失效缓存配合抽象类型RouterLike只管“怎么调”UtilsLike管“怎么失效/写缓存”通用组件应同时以 props 接收两者如示例中props.utils.listThings.invalidate()的调用。工厂命名空间差异化只要工厂产出的路由结构同构无论它嵌套多深trpc.deep.route.things也无论挂在几个命名空间下different.things抽象类型都能让组件保持单一实现——这是将“复用”从数据层提升到类型层的核心手段。以测试为参照想落地生产级多态路由直接阅读 packages/react-query/test/polymorphism.test.tsx 与 packages/react-query/test/polymorphism.factory.tsx 比照示例是最快的上手路径。6. 延伸阅读路由层通用推断inferRouterInputs/inferRouterOutputs/TRPCClientErrorwww/versioned_docs/version-10.x/client/vanilla/infer-types.mdReact Query 集成安装与初始化www/versioned_docs/version-10.x/client/react/setup.mdxuseUtilsinvalidate、refetch、setData等APIwww/versioned_docs/version-10.x/client/react/useUtils.mdx相关 hooks 文档useQuery、useMutation、useQueries、useInfiniteQuery、suspense见 www/versioned_docs/version-10.x/client/react 目录核心实现文件packages/react-query/src/utils/inferReactQueryProcedure.ts、packages/react-query/src/shared/polymorphism/routerLike.ts、packages/react-query/src/shared/polymorphism/utilsLike.ts【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表