Chapter 01

tRPC 核心理念

理解端到端类型安全的本质,掌握 tRPC 解决前后端类型同步的革命性方法

1.1 前后端类型同步的痛点

在传统全栈开发中,前后端之间存在一道"类型鸿沟"。后端定义了接口,前端需要手动维护一份平行的类型声明,两者极易出现不一致。这是全栈 TypeScript 项目中最常见的痛点之一。

典型问题场景

假设后端修改了用户对象,将 username 字段重命名为 name

// 后端修改了接口(Express 示例)
app.get('/api/user/:id', (req, res) => {
  res.json({
    id: 1,
    name: 'Alice',        // 原来是 username,改名了
    email: 'alice@example.com'
  });
});

// 前端类型没有同步更新 ——运行时才发现错误
interface User {
  id: number;
  username: string;  // 过期!但 TypeScript 不报错
  email: string;
}

const user = await fetchUser(1);
console.log(user.username);  // undefined,但 TS 不报错!
TS
🚨

运行时才爆炸TypeScript 提供了类型安全,但前提是类型定义本身是正确的。如果前端的类型定义与后端实际返回不一致,TypeScript 无法帮你,错误只会在运行时出现。这就是"前后端类型同步"问题的本质。

现有解决方案的局限

业界已有几种解决方案,但各有缺陷:

1.2 tRPC 是什么

tRPC(TypeScript Remote Procedure Call)是一个让你无需任何代码生成就能在客户端和服务端之间共享类型的库。它的核心思路极其简单:

后端的函数签名,直接成为前端的类型。

这不是魔法,而是 TypeScript 的 infer 类型推断能力。前端代码直接导入后端 Router 的类型(注意:只是类型,不是运行时代码),TypeScript 编译器在构建时帮你完成了"代码生成"。

后端
procedure.query()
类型导出
AppRouter
前端类型推断
inferRouterOutputs
前端调用
trpc.user.get.useQuery()
// 后端定义 ——这是唯一的真相来源
const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.number() }))
    .query(async ({ input }) => {
      return { id: input.id, name: 'Alice', email: 'alice@example.com' };
      //  ↑ 返回类型自动推断,无需手动声明
    }),
});

export type AppRouter = typeof appRouter;

// 前端使用 ——类型完全自动推断
const { data: user } = trpc.user.getById.useQuery({ id: 1 });
//      ^? { id: number; name: string; email: string } | undefined
user?.name;    // ✅ 自动补全,类型正确
user?.username; // ✅ TypeScript 编译报错!立即发现问题
TS

1.3 tRPC vs REST vs GraphQL 核心对比

维度 REST GraphQL tRPC
类型安全手动维护(易过期)需要代码生成自动推断,零维护
学习曲线中高(SDL、解析器)低(会 TypeScript 即可)
开发速度初期慢,后期快最快
API 文档需要 OpenAPI/Swagger自带 Introspection类型即文档
公开 API最佳选择适合不适合
多语言客户端支持支持仅 TypeScript
实时支持需额外实现SubscriptionWebSocket Subscription
包体积零额外依赖较大极小(~5KB)
ℹ️

选型建议tRPC 不是 REST 或 GraphQL 的竞争者,而是一个专门为全栈 TypeScript 团队内部 API设计的工具。如果你的前后端都是 TypeScript,且不需要对外公开 API,tRPC 几乎是最优选择。

1.4 工作原理:TypeScript 类型推断 + HTTP

tRPC 底层仍然是普通的 HTTP 请求,没有任何黑魔法。它使用 HTTP GET 执行 Query,使用 HTTP POST 执行 Mutation。类型安全完全发生在编译时,运行时只是普通的 JSON 请求。

// tRPC Query 的实际 HTTP 请求
// GET /api/trpc/user.getById?input={"id":1}
// Response: {"result":{"data":{"id":1,"name":"Alice","email":"alice@example.com"}}}

// tRPC Mutation 的实际 HTTP 请求
// POST /api/trpc/user.create
// Body: {"name":"Bob","email":"bob@example.com"}
// Response: {"result":{"data":{"id":2,"name":"Bob","email":"bob@example.com"}}}
HTTP

类型推断的魔法

前端代码通过 import type { AppRouter } from '../server/router' 导入后端 Router 的类型。注意这是 import type,纯类型导入,不会产生任何运行时代码,不会把服务端代码打包进前端。TypeScript 编译器利用这个类型信息,在构建时进行全链路类型检查。

// 类型只在编译时使用,不产生运行时代码
import type { AppRouter } from '../server/router'

// createTRPCReact 利用 AppRouter 类型创建类型安全的客户端
const trpc = createTRPCReact<AppRouter>()

// 编译器推断出 trpc.user.getById.useQuery 的参数和返回值类型
// 完全来自后端定义,零冗余代码
TS

1.5 适用场景与不适用场景

适合使用 tRPC

  • 前后端都是 TypeScript 的全栈项目
  • Next.js / Remix 全栈应用
  • 小团队快速迭代,不想维护 API 文档
  • 团队内部微服务通信(全 TS 环境)
  • 需要极致开发效率的 SaaS 产品

不适合使用 tRPC

  • 需要对外公开的 API(第三方接入)
  • 客户端包含非 TypeScript 语言
  • 需要严格的 RESTful 规范(如与安全团队约定)
  • 已有大量 REST API 且团队熟悉 OpenAPI
  • 移动端(iOS/Android 原生)为主要客户端

1.6 安装与基础依赖

tRPC 由多个包组成,按需安装。最常见的组合是 Next.js + React:

# 核心包
pnpm add @trpc/server @trpc/client

# React / Next.js 客户端集成
pnpm add @trpc/react-query @tanstack/react-query

# 输入验证(强烈推荐)
pnpm add zod

# Next.js HTTP 适配器(内置于 @trpc/server)
# 无需额外安装
SHELL
包名用途
@trpc/server服务端核心,定义 Router 和 Procedure
@trpc/client客户端核心,发起类型安全的请求
@trpc/react-queryReact 集成,提供 useQuery/useMutation Hooks
@tanstack/react-query@trpc/react-query 的必要 peer dependency
zod运行时输入验证,与 tRPC 深度集成

1.7 核心名词解释

💡

本章小结tRPC 的核心价值是利用 TypeScript 类型推断消除前后端类型同步的人工成本。它不是代码生成工具,而是一个将后端类型定义直接暴露给前端的框架约定。底层仍是普通 HTTP 请求,运行时零开销。适合全栈 TypeScript 项目,不适合多语言环境或公开 API 场景。