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 无法帮你,错误只会在运行时出现。这就是"前后端类型同步"问题的本质。
现有解决方案的局限
业界已有几种解决方案,但各有缺陷:
- 手动维护类型:最常见,也最容易出错。类型变更需要同时修改前后端,极易遗漏。
- OpenAPI + 代码生成:需要维护额外的 YAML/JSON Schema,代码生成步骤繁琐,生成代码可读性差。
- GraphQL + 代码生成:需要定义 Schema,运行 codegen,开销大。适合公开 API,但对内部全栈项目过重。
1.2 tRPC 是什么
tRPC(TypeScript Remote Procedure Call)是一个让你无需任何代码生成就能在客户端和服务端之间共享类型的库。它的核心思路极其简单:
后端的函数签名,直接成为前端的类型。
这不是魔法,而是 TypeScript 的 infer 类型推断能力。前端代码直接导入后端 Router 的类型(注意:只是类型,不是运行时代码),TypeScript 编译器在构建时帮你完成了"代码生成"。
// 后端定义 ——这是唯一的真相来源
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 |
| 实时支持 | 需额外实现 | Subscription | WebSocket 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-query | React 集成,提供 useQuery/useMutation Hooks |
@tanstack/react-query | @trpc/react-query 的必要 peer dependency |
zod | 运行时输入验证,与 tRPC 深度集成 |
1.7 核心名词解释
- Procedure tRPC 中的"端点"概念,类似 REST 中的一个路由处理函数。分三类:Query(读操作,GET)、Mutation(写操作,POST)、Subscription(实时,WebSocket)。
- Router 将多个 Procedure 组织在一起的容器,类似 Express 的 Router。可以嵌套,通过 mergeRouters 或嵌套 router 对象实现模块化拆分。
-
AppRouter
根 Router 的类型导出,是整个 tRPC 类型系统的"入口"。前端通过
import type { AppRouter }获得完整的类型推断能力。 -
Context
每个请求都会创建的上下文对象,包含用户信息、数据库连接、会话等。所有 Procedure 都可以通过
ctx参数访问。类似 Express 的 req 对象扩展。 - Middleware 在 Procedure 执行前运行的函数,可以修改 Context、进行权限检查、记录日志、实现缓存等。多个 Middleware 按顺序组成中间件链。
-
Input Validation
通过
.input(zodSchema)定义 Procedure 的输入类型。Zod Schema 同时完成运行时验证和 TypeScript 类型推断,是类型安全的保障。 - Link 客户端的传输层配置,决定请求如何发送。常用的有 httpBatchLink(批量 HTTP 请求)、httpLink(单次 HTTP 请求)、wsLink(WebSocket 连接)。
- Caller 服务端直接调用 Procedure 的方式,绕过 HTTP 层。主要用于单元测试和 Server Component 中的服务端直调,性能更高。
本章小结tRPC 的核心价值是利用 TypeScript 类型推断消除前后端类型同步的人工成本。它不是代码生成工具,而是一个将后端类型定义直接暴露给前端的框架约定。底层仍是普通 HTTP 请求,运行时零开销。适合全栈 TypeScript 项目,不适合多语言环境或公开 API 场景。