首页
/ 使用 @trpc/react-query:在 React 应用中搭建端到端类型安全的 tRPC 数据层

使用 @trpc/react-query:在 React 应用中搭建端到端类型安全的 tRPC 数据层

2026-09-08 13:42:25作者:牧宁李

本篇技术指南围绕 tRPC 官方 React 集成包 @trpc/react-query 展开,讲解如何在 React 项目中把 tRPC 的端到端类型安全过程调用与 TanStack Query(react-query)的缓存、失效与状态管理能力结合起来。你会依次掌握包的安装方式、从创建类型化 hooks 到接入 Provider、再到在组件中发起 useQuery 调用的三步走用法,并通过阅读仓库源码理解代理解码、查询键生成、请求派发链路以及 useUtils 等命令式工具的设计原理。读完即可在自己的 React / Next.js 全栈项目里落地一套可复制、可运行的"零手工 API 层"实践。

一、@trpc/react-query 是什么

@trpc/react-query 在 tRPC 仓库中定义为一个 "tRPC wrapper around react-query",即"基于 react-query 的 tRPC React 封装"(见 packages/react-query/README.md)。它要解决的核心问题是:让 React 组件直接用 trpc.xxx.useQuery() 这种符合直觉的调用形式去发起远程过程调用,同时把 tRPC 的类型信息完整地透传到 TanStack Query 的 Hooks 之上,让输入、输出、错误类型在编译期全程可推导。

需要说明的是,tRPC v11 在保留本包的同时,还演进出了另一条以 @trpc/tanstack-react-query 为代表的"新一代"集成路线(其技能文档见 packages/tanstack-react-query/skills)。@trpc/react-query 则是文档中沿用了多个大版本的"经典"封装形态,其 API 以 createTRPCReact 为中心,本文所讲内容与仓库版本 11.18.0 保持一致(见 packages/react-query/package.json)。

从依赖关系看,本包将以下包声明为 peer dependency:

依赖 版本要求 作用
@tanstack/react-query ^5.80.3 提供 QueryClientuseQuery 等底层能力
@trpc/client 11.18.0 提供 tRPC 客户端与 HTTP 传输
@trpc/server 11.18.0 提供类型层面的 Router / Procedure 定义
react >=18.2.0 React 运行时
typescript >=5.7.2 提供类型推导所需编译能力

因此,@trpc/react-query 本身不重复实现数据缓存或请求传输,它是把 tRPC 的 Router 类型与 TanStack Query v5 的数据获取语义"焊"在一起的类型层和胶水层。

二、安装

在项目根目录执行任一包管理器命令即可(四个包管理器命令均为仓库 README 原始内容):

# npm
npm install @trpc/react-query @tanstack/react-query

# Yarn
yarn add @trpc/react-query @tanstack/react-query

# pnpm
pnpm add @trpc/react-query @tanstack/react-query

# Bun
bun add @trpc/react-query @tanstack/react-query

安装时请把 @tanstack/react-query 一并显式安装,因为它作为 peer dependency 不会被自动带入。包同时提供多种子路径导出:@trpc/react-query(主入口)、@trpc/react-query/rsc(React Server Components 支持,对应源码 packages/react-query/src/rsc.tsx)、@trpc/react-query/server@trpc/react-query/shared(见 packages/react-query/package.json 中的 exports 字段)。

三、基础示例:三步把类型安全接到 React 组件

第一步:创建导出 hooks 的 utils 文件

与 tRPC 惯例一致,先创建一个工具文件,用服务端导出的 AppRouter 类型去实例化一个全局单例:

import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from './server';

export const trpc = createTRPCReact<AppRouter>();

这里的 createTRPCReact 正是本包最核心的入口(实现见 packages/react-query/src/createTRPCReact.tsx)。它返回的并不是一个普通对象,而是一个"扁平代理"(flat proxy):你对 trpc.greetingtrpc.user.list 这类路径的属性访问会被代理拦截并记录为路径数组,最终路由到对应 Procedure 的 Hooks 上。换句话说,只要 AppRouter 中注册了某个 query/mutation/subscription,代理上就会自动出现对应类型的调用入口,无需手写任何客户端定义。

从源码结构看,createTRPCReact 内部实际做了两层组合(见 packages/react-query/src/createTRPCReact.tsx#L508-L518):

  1. createRootHooks(opts) 创建包含 ProvideruseQueryuseMutation 等真实实现的一组 hooks(见 packages/react-query/src/shared/hooks/createHooksInternal.tsx);
  2. createHooksInternal 再用 createFlatProxy 包一层,把 useUtils / useContextProvidercreateClient 等固定成员与根据 Router 类型生成的 procedure 装饰对象合并到一个统一的代理对象上(见 packages/react-query/src/createTRPCReact.tsx#L477-L506)。

第二步:用 Provider 把客户端与 QueryClient 注入应用

在应用根部,通过 trpc.createClient 创建 tRPC 客户端,并把它与一个 TanStack Query 的 QueryClient 一起挂在 trpc.Provider 下:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { trpc } from '~/utils/trpc';
import React, { useState } from 'react';

export function App() {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    trpc.createClient({
      url: 'http://localhost:5000/trpc',
    }),
  );
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        {/* Your app here */}
      </QueryClientProvider>
    </trpc.Provider>
  );
}

几个值得注意的工程细节:

  • 为什么要用 useState 初始化new QueryClient()trpc.createClient() 都应该只创建一次,避免每次渲染都重建导致缓存与连接状态丢失;useState 的惰性初始化正好满足这一需求。
  • Provider 的职责TRPCProvider 会把 clientqueryClientssrContextssrStateabortOnUnmount 等组装成一个 context 值下发给所有后代组件(结构定义见 packages/react-query/src/internals/context.tsx#L73-L132,Provider 实现见 packages/react-query/src/shared/hooks/createHooksInternal.tsx#L100-L140)。
  • 为什么还要包一层 QueryClientProvider@trpc/react-query 的 hooks 最终仍会委托给 @tanstack/react-queryuseQuery / useMutation 等 hooks,因此组件树中必须存在 TanStack Query 自己的 Provider。这个示例还演示了在开启 SSR 时如何用 useState 生成带自定义 defaultOptions 的 QueryClient(仓库中 examples/next-sse-chat/src/lib/query-client.ts 演示了为 SSR 设置 staleTime 的做法)。

若你的项目服务端基于 standalone/fetch 适配器运行(例如 localhost:5000/trpc 上的 tRPC HTTP 端点),客户端传输层链接配置还可以传入 links 数组来自定义,这里用最简的 url 方式即可启动。

第三步:在组件里发起类型安全的查询

完成注入后,任何组件都能用 useQuery 拿到后端 query 的数据与状态:

import { trpc } from '~/utils/trpc';

export function Hello() {
  const { data, error, status } = trpc.greeting.useQuery({ name: 'tRPC' });

  if (error) {
    return <p>{error.message}</p>;
  }

  if (status !== 'success') {
    return <p>Loading...</p>;
  }

  return <div>{data && <p>{data.greeting}</p>}</div>;
}

这里 trpc.greeting 的参数对象 { name: 'tRPC' } 的类型与后端过程声明的输入校验 schema 完全一致,data.greeting 的类型也直接来自后端输出类型——全链路无手工接口契约。data / error / status 取自 TanStack Query v5 的结果形态,因此你熟悉的 isLoadingisErrorrefetch 等能力全部可用;同时 hooks 返回结果还被额外装饰了 trpc.path 等元信息(源码中通过 useHookResult 实现,见 packages/react-query/src/shared/hooks/createHooksInternal.tsxhook.trpc = useHookResult(...) 的赋值)。

四、深入源码:Procedure 是如何被"装饰"成 Hooks 的

类型安全不是魔法,而是建立在 TypeScript 条件类型 + 运行时代理两层机制之上。

运行时侧:以 createTRPCReact 为例,任何对代理的链式访问最终都会落到 createReactDecoration 生成的递归代理上(见 packages/react-query/src/shared/proxy/decorationProxy.ts):

return createRecursiveProxy(({ path, args }) => {
  const pathCopy = [...path];
  const lastArg = pathCopy.pop()!; // 例如 'useQuery' / 'useMutation'

  if (lastArg === 'useMutation') {
    return hookslastArg;
  }
  if (lastArg === '_def') {
    return { path: pathCopy }; // 供 getQueryKey 等工具取路径
  }
  const [input, ...rest] = args;
  return hookslastArg;
});

也就是说,trpc.user.list.useQuery({...}) 会被规约成对 useQuery(['user','list'], input, opts) 的调用,路径中的每一段(包括嵌套 router)都被保留下来,用于之后拼装查询键。

类型侧DecorateProcedure 依据 Procedure 的 type 三态分发(见 packages/react-query/src/createTRPCReact.tsx#L403-L441):

  • queryDecoratedQuery,拥有 useQueryusePrefetchQueryuseSuspenseQuery,以及当输入含 cursor 时的 useInfiniteQuery 系列;
  • mutationDecoratedMutation,拥有 useMutation
  • subscription → 拥有 useSubscription,其数据类型基于输出异步迭代器的 yield 类型推导。

DecoratedQueryMethods(见 packages/react-query/src/createTRPCReact.tsx#L322-L345)为每个 query 方法同时提供普通结果与元数据 TRPCHookResult,并把 input/output/errorShape/transformer 从 Procedure 定义中逐个提取出来,保证每个 hooks 的泛型签名都是闭合且精确的。

五、查询键与请求链路:从 React 状态到 HTTP 调用

Hooks 拿到"路径数组 + input"之后,如何变成一次真实的 tRPC 请求?关键在两步:

1. 生成查询键(Query Key)getQueryKeyInternal(见 packages/react-query/src/internals/getQueryKey.ts)把 tRPC 过程路径结构化为 TanStack Query 可缓存的结构化键:

export type TRPCQueryKey = [
  readonly string[],                     // 例如 ['user','list']
  { input?: unknown; type?: 'infinite' | 'query' }?,
];

设计上刻意把 input 独立成第二个数组元素并标记 type,从而支持对"同一个 router 下所有查询"做整体失效——当没有 input 且 type 为 any 时键会被压缩成 [path],这正对应 utils.invalidate() 想命中整棵子树的场景。此外,无限查询会从 input 中剔除保留字段 cursordirection 再入键,避免翻页参数污染缓存键。包从主入口还导出了供你在应用层显式使用的 getQueryKey / getMutationKey(见 packages/react-query/src/index.ts),mutation 的键形如 [path]

2. 派发请求。真正的网络调用发生在 react-query 提供的 queryFn 内部(见 packages/react-query/src/shared/hooks/createHooksInternal.tsxuseQuery 实现):通过 getClientArgsTRPCQueryKey 还原为 [path.join('.'), input, opts.trpc] 元组(见 packages/react-query/src/internals/getClientArgs.ts),再调用底层 tRPC 客户端(@trpc/clientcreateTRPCClient,即 Provider 上暴露的 createClient)。opts.trpc 这一命名空间承载了 ssrabortOnUnmountsignal 等 tRPC 专有选项,与 TanStack Query 自身的选项互不干扰。

六、Hooks 全景:查询、变更、订阅与无限查询

基于仓库实现,@trpc/react-query 的装饰层按 Procedure 类型提供如下 Hooks(全部位于 packages/react-query/src/shared/hooks/createHooksInternal.tsx):

Hooks 适用 Procedure 关键行为与扩展点
useQuery(input?, opts?) query 委托 @tanstack/react-queryuseQuery;支持 skipToken 作 input 以禁用请求;输出为异步迭代器时会被转成可缓存结果
useSuspenseQuery query 基于 React Suspense 的形态,返回 [data, result] 元组,供 <Suspense> 场景使用
usePrefetchQuery query 借助 TanStack v5 的 usePrefetchQuery 在渲染期间预取,不触发额外状态
useInfiniteQuery cursor 输入的 query 自动把 react-query 的 pageParam/direction 映射为 tRPC 输入的 cursor/direction 保留字段;用 opts.initialCursor 指定首页参数
useSuspenseInfiniteQuery / usePrefetchInfiniteQuery 同上 Suspense 与预取变体
useMutation(opts?) mutation 委托 useMutationmutationFn 内部走 client.mutation;支持通过 config.overrides.useMutation.onSuccess 全局改写成功回调
useSubscription(input?, opts?) subscription 直接订阅 tRPC 底层流(client.subscription),结果状态机含 idle/connecting/pending/error;依赖 enabledskipToken 控制开关,卸载时自动退订
useQueries(fn) / useSuspenseQueries(fn) query 集合 用回调 + 类型化代理同时声明多个查询(实现见 packages/react-query/src/internals/useQueries.tspackages/react-query/src/shared/proxy/useQueriesProxy.ts

其中有两个设计细节值得展开:

  • SSR 预取:当运行在服务端且 ssrState === 'prepass'opts.trpc.ssr !== falseenabled 不为 false 且缓存中无该键时,useQuery/useInfiniteQuery/useQueries 会自动触发 prefetchQuery,把数据在渲染前填充进 QueryClient。这也是与 Next.js 等框架配合实现"首屏即数据"的基础。
  • abortOnUnmount:组件卸载时是否中止进行中的请求,可通过 opts.trpc.abortOnUnmountcreateTRPCReact 全局配置或 Provider 的 abortOnUnmount prop 三级控制;启用时会把 react-query 的 AbortSignal 透传给 tRPC 客户端(相关代码在 createHooksInternal.tsx 中多次出现的 shouldAbortOnUnmount 逻辑处)。Provider 上也提供了 ssrContext(SSR 请求上下文,如 req/res)与 ssrState(取值为 'mounted' | 'mounting' | 'prepass' | false)两个选项,类型定义与默认值见 packages/react-query/src/internals/context.tsx#L70-L98

七、useUtils:脱离组件也能管理缓存

类组件或事件处理器里需要主动刷新数据时,可以调用 trpc.useUtils()useContext 是其已弃用别名,二者类型定义见 packages/react-query/src/createTRPCReact.tsx#L446-L461)。它返回的上下文同时携带了 Provider 注入的全部能力,以及按 Router 形状类型化生成的工具集合,常用写法例如"mutation 成功后让相关查询失效":

const utils = trpc.useUtils();

const mutation = trpc.post.create.useMutation({
  onSuccess: () => {
    // 让 post 路由下所有查询缓存失效并自动重新拉取
    utils.post.invalidate();
  },
});

这些命令式方法在 packages/react-query/src/utils/createUtilityFunctions.ts 中逐一实现并直接委托给 QueryClient

  • 预取/读取:prefetchQueryprefetchInfiniteQueryensureQueryDatafetchQueryfetchInfiniteQuerygetQueryDatagetInfiniteQueryData
  • 失效与刷新:invalidateQueriesrefetchQueriesresetQueriescancelQuery
  • 就地写入:setQueryDatasetQueriesDatasetInfiniteQueryData
  • 类型安全的选项工厂:queryOptionsinfiniteQueryOptions(自动装配 queryKeyqueryFn);
  • mutation 默认值管理:setMutationDefaultsgetMutationDefaultsisMutating

需要注意,像 invalidate 这种挂在 utils.<routerPath> 下的链式入口由另一层类型化代理 utilsProxy 提供(见 packages/react-query/src/shared/proxy/utilsProxy.ts),它会按 [path] 前缀精确命中整个子树。

如果你需要在组件之外(例如路由层或事件处理中)创建带类型的工具对象,包还导出了 createTRPCQueryUtils(见 packages/react-query/src/createTRPCQueryUtils.tsx),传入 { client, queryClient } 即可获得与 useUtils() 相同能力但与 React 生命周期解耦的实例。

八、服务端调用、RSC 与真实示例

@trpc/react-query 在 SSR 与 Server Components 场景下需要额外的类型化调用手段:包通过子路径导出 ./server./rsc,其中服务端侧实现(含基于代理的 ssgProxy)位于 packages/react-query/src/server,RSC 入口为 packages/react-query/src/rsc.tsx,适用于在服务端组件中直接发起"脱水"查询、避免把敏感数据下发给浏览器。Next.js App Router / Pages Router 的完整接入方式在仓库 www/docs/client/reactpackages/next/skills 目录下有配套说明。

仓库中还提供了真实的端到端范例:例如 examples/next-sse-chat/src/lib/trpc.ts 中的 export const trpc = createTRPCReact<AppRouter>()examples/next-sse-chat/src/lib/query-client.ts 中针对 SSR 定制 staleTimecreateQueryClient,它们与本文的三步走骨架完全同构,可直接作为参照;后续再搭配 packages/tanstack-react-query/skills/react-query-classic-migration 的迁移说明,即可在理解经典封装的基础上平滑演进到新一代 @trpc/tanstack-react-query 集成。

小结

一句话总结这套模式的收益:Router 定义即唯一契约@trpc/react-query 通过 createTRPCReact<AppRouter>() 把服务端过程注册表转化为组件可直接消费的类型化 hooks,再把 react-query 的缓存、失效、Suspense、预取语义完整保留下来,让"写后端"与"调接口"之间不再需要任何手写桥接层。对 React 技术栈的读者而言,建议从第三步组件示例入手跑通最小链路,再逐步深入第六、七节的缓存管理与 SSR 细节,即可在真实项目中稳定落地。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
924
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
599
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
394