RPC、区块链 RPC 与 tRPC
# RPC、区块链 RPC 与 tRPC
RPC 是远程调用的通用概念,区块链 RPC 是访问节点的接口,tRPC 是让 TypeScript 前后端共享接口类型的工具。它们有关联,但不是同一个东西。
# RPC 是什么
RPC 全称是 Remote Procedure Call,中文叫 “远程过程调用” 。通俗地说,就是向另一台机器发送请求,让它执行一个操作,再把结果返回来。例如网页想查询课程,就把课程 ID 发给后端,由后端查出课程资料后返回。
RPC 是一种调用方式,不是某个具体框架,也不是区块链专属技术;tRPC 则是实现这种调用方式的具体工具,重点是让 TypeScript 前后端共享接口类型。两者不是两种互相替代的技术,也不能因为都带 RPC,就认为用的是同一套接口。
# 区块链笔记里的 RPC 指什么
Web3 项目中说 “配置 RPC” ,通常是指配置区块链节点的访问地址,也就是 RPC URL。应用通过这个地址向节点查询链上数据或提交交易;节点可以自己运行,也可以使用 Alchemy、Infura 等服务商提供的节点服务。
以太坊节点通常提供 JSON-RPC 接口:用 JSON 描述 “调用哪个方法、传什么参数” ,节点执行后返回结果或错误。例如:
- 查 ETH 余额:调用
eth_getBalance,传入钱包地址和要查询的区块。 - 读合约:调用
eth_call,例如读取课程价格;这是在节点上执行调用以取得结果,不会把状态变更写入链上。 - 发交易:调用
eth_sendRawTransaction,提交已经签名的交易。返回交易哈希后,还要查询回执和确认状态,不能直接认定业务成功。
平时使用 viem、ethers.js 时,库会帮我们组织这些请求,不必手写每条 JSON 消息。RPC URL 不是合约地址:前者决定请求发给哪个节点服务,后者决定在对应网络上访问哪个合约。提交签名后的交易也不需要把私钥交给 RPC 服务商。
# tRPC 是什么
tRPC(Typed RPC) (opens new window)是一个用于 TypeScript 的端到端类型安全的远程过程调用(RPC)框架。它强调使用 TypeScript 的类型系统,通过在 TypeScript 中定义接口来确保远程调用的类型安全性。
# tRPC 的类型提示是怎么来的
以查询课程为例,一次调用可以分成四步:
- 后端定义课程查询接口,规定输入的
id是字符串,返回课程标题等字段,并导出整套接口的AppRouter类型。 - 前端通过
import type引用这个类型,创建 tRPC 客户端,因此知道有哪些接口、每个接口接受和返回什么数据。共享的是类型,不是把数据库代码放进浏览器。 - 前端调用课程查询方法时,客户端仍然会发送网络请求,由后端查询数据库并返回结果,不是在浏览器里直接执行后端函数。
- 如果把
id错传成数字,TypeScript 检查时就能发现;后端改了返回字段,使用同一份最新接口类型的前端也能及时发现调用不匹配。
这就是 “端到端类型安全” 的主要好处:前后端不必各自手写一套接口类型,许多对接错误可以提前发现。但网络仍可能超时,旧版客户端也可能还在使用;请求输入仍需 Zod 等运行时校验,身份和权限仍需后端检查。
# tRPC 的能力与边界
1. 类型安全
tRPC 利用 TypeScript 的类型系统,在类型检查时发现参数和返回值使用不匹配的问题,减少运行后才发现的对接错误;这不代表运行时数据和业务逻辑一定正确。
2. 不需要额外的代码生成步骤
tRPC 直接利用后端接口类型和 TypeScript 的类型推导提供客户端提示,不需要先生成一套客户端、服务端代码,也不用在前端重复维护相同的参数和返回值类型。
3. 减少文档不一致性
接口类型能直接说明字段名称和类型,减少文档与字段定义不一致的问题;但 “谁有权限调用” 、“失败后能否重试” 等业务规则仍需要文档,不能靠类型代替。
4. 开发效率提升
使用强类型的远程调用接口,可以在开发过程中获得智能感知、自动完成和类型检查等功能,从而提高开发效率。你可以在编辑器中直接看到可用的远程调用方法,以及它们的输入和输出类型。
5. 错误处理
tRPC 提供了强大的错误处理机制。你可以定义各种错误类型,并在服务端和客户端上进行捕获和处理。这有助于更好地处理错误情况,提高应用的稳定性。
6. 扩展性
tRPC 是可扩展的,支持插件和中间件。这意味着你可以根据自己的需求自定义和扩展功能,使 tRPC 更好地适应你的项目要求。
7. 支持多种传输协议
tRPC 支持多种传输协议,包括 HTTP、WebSocket 等,使其适用于不同的应用场景。你可以选择最适合你项目的传输方式。
# RPC、区块链 RPC 和 tRPC 怎么区分
| 名称 | 含义与例子 |
|---|---|
| RPC | 远程调用的一般概念,例如请求远程服务执行查询,不限定业务和框架 |
| 区块链 RPC | 应用访问区块链节点的接口,用于查余额、读合约、提交签名交易 |
| tRPC | TypeScript 前后端共享接口类型的工具,例如网页向自己的后端查询课程、保存学习进度 |
在 Web3 大学项目里,网页通过 tRPC 找自己的后端,应用通过区块链 RPC 找链上节点。一次课程列表请求也可以先到后端,再由后端读取链上课程状态并合并数据库资料。这两层可以同时存在;tRPC 本身不会自动读取区块链。项目中的具体分工见 Web3 大学:tRPC v11。
# 面试时怎么解释 RPC 和 tRPC
建议回答:RPC 是远程过程调用,就是让远程服务执行操作并返回结果。在 Web3 项目里,区块链 RPC 主要用来访问节点,比如查余额、读合约和发送交易;tRPC 则用于我们自己的 TypeScript 前后端接口,让前端直接获得后端参数和返回值的类型,减少接口对接错误。比如查询课程资料可以走 tRPC,查询链上余额走节点 RPC。它们服务于不同的调用环节,可以同时使用;类型检查也不能代替后端的输入校验和权限检查。
# Zod 与 tRPC 的联系和区别
tRPC 负责接口调用和前后端类型共享,Zod 负责检查实际数据是否符合规则。两者经常搭配使用,但不是同一种工具,也不能互相替代。
- tRPC:让前端知道后端有哪些接口、参数和返回值是什么类型。例如接口要求课程 ID 是字符串,前端传成数字,TypeScript 在开发时就能提示错误;真正执行请求时,仍由 tRPC 客户端与后端通信。
- Zod:运行时数据校验库,用 Schema(数据校验规则)规定字段的类型、格式和范围。例如课程 ID 不仅必须是字符串,还不能为空;请求到达后端后,要检查收到的真实数据,而不是相信前端传得一定正确。
- 一起使用:把 Zod Schema 交给 tRPC 的
.input(),tRPC 就能从它推导输入类型,并在调用接口时执行校验。同一份规则既提供类型提示,也检查实际输入,不必再手写一份相同的参数类型。
TypeScript 类型编译后会被擦除。用户可以绕过网页直接请求接口,所以开发时没有类型报错,不代表后端收到的数据一定合法。Zod 自身也能推导 TypeScript 类型,但不负责网络请求;tRPC 支持多种校验工具,并不强制使用 Zod。Zod 的独立用法见 Zod 是什么:Schema、parse 与 safeParse。
例子:给课程查询接口加上输入校验。下面是后端接口定义,依赖 @trpc/server 和 zod。示例用内存数组代替数据库,只展示接口定义与校验;对外提供 HTTP 服务时,还需接入 tRPC 的服务端适配器。
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
const t = initTRPC.create()
// 示例数据;实际项目在查询函数里改为访问数据库。
const courses = [{ id: 'course-1', title: '智能合约入门' }]
export const appRouter = t.router({
courseById: t.procedure
// 必须是字符串,去掉首尾空格后至少有一个字符。
.input(z.object({ id: z.string().trim().min(1) }))
.query(({ input }) => {
// 只有校验通过才会执行这里;input.id 的类型自动推导为 string。
return courses.find(course => course.id === input.id) ?? null
}),
})
// 前端通过 import type 引用此类型,获得接口参数与返回值提示。
export type AppRouter = typeof appRouter
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
前端使用带有 AppRouter 类型的 tRPC 客户端调用 courseById.query({ id: 'course-1' }) 时,参数会经网络发到后端。对这个接口来说:
- 传
{ id: 123 }:正常编写 TypeScript 前端代码时会报类型错误;即使绕过前端直接请求,后端的 Zod 校验也会拒绝。 - 传
{ id: '' }或只有空格:它们的类型仍是字符串,TypeScript 不会因为空字符串就报错,但 Zod 会按规则拒绝。 - 传
{ id: 'course-1' }:校验通过后才进入查询;如果传的是不存在的课程 ID,示例返回null。数据格式正确不代表课程一定存在,也不代表用户有权访问课程内容,业务和权限检查仍需另外处理。
面试题:已经用了 tRPC,为什么还需要 Zod?
建议回答:tRPC 让前后端共享接口类型,提前发现参数和返回值使用错误,但 TypeScript 类型在运行时会被擦除,不能保证真实请求一定合法。Zod 用来检查实际收到的数据,比如课程 ID 必须是非空字符串。把 Zod 规则传给 tRPC 的 input 后,同一份规则既能推导输入类型,也能在后端校验请求,减少重复定义。不过校验通过只说明数据符合规则,权限和业务条件仍要另外检查。
官方参考:tRPC 输入校验 (opens new window)、Zod 数据校验与类型推导 (opens new window)。
# GraphQL 与 tRPC 的联系和区别
GraphQL (opens new window) 是一种面向 API 的查询语言及执行规范。通俗地说,前端在请求里说明 “我要哪些字段” ,后端按这个结构组织返回结果。
可查询的对象、字段和类型由后端的 Schema(接口结构与类型约定)定义;实际取数由服务端的 Resolver(字段解析函数)负责。它不是让前端直接查询数据库,也不是想要什么字段就能拿到什么,仍然受接口定义和权限控制。
它们都能用于前后端的数据交互,但不是上下级关系,tRPC 也不是基于 GraphQL 实现的。两者都能查询、修改数据,并通过类型约束减少接口对接错误;GraphQL 更强调客户端按需选择数据,tRPC 更强调 TypeScript 前后端直接共享接口类型。
# 用查询课程理解区别
假设课程有标题、简介、价格和教师信息,而列表页只需要课程标题与教师姓名:
- GraphQL:前端在查询中选择这两个字段,接口通常就只返回对应的数据。如果详情页还需要简介和价格,且 Schema 已提供这些字段,前端可以在查询中加上它们。
- tRPC:前端调用后端定义好的课程查询方法,返回哪些字段由这个方法决定。前端只读取结果中的标题,不代表网络请求就不会传回其他字段。也可以由后端提供精简的列表接口,或专门设计选字段的参数,但这不是 tRPC 自动提供的查询语言能力。
一句话记忆:GraphQL 是 “在接口允许的范围内选数据” ,tRPC 是 “带着类型提示调用后端方法” 。
# 主要区别与适用场景
| 对比维度 | GraphQL | tRPC |
|---|---|---|
| 核心能力 | 客户端按 Schema 选择字段和关联数据 | 客户端调用后端定义的 procedure(接口方法) |
| 返回字段 | 由客户端的查询选择,受 Schema 和权限限制 | 由接口实现决定;要支持字段选择,需自行设计 |
| 类型来源 | Schema 定义接口类型;TypeScript 项目常用工具生成查询结果类型 | 从后端 TypeScript 接口定义直接推导,通常不需要代码生成 |
| 语言适配 | 有多种语言的实现,不要求前后端都用 TypeScript | 共享类型的主要优势面向 TypeScript 前后端 |
| 维护工作 | 维护 Schema 和字段取数逻辑,并控制关联查询开销 | 维护业务接口和类型共享,也要做好输入校验与权限控制 |
| 常见选择 | 多种客户端的数据需求差异较大,需要灵活选择字段 | TypeScript 全栈、同一团队协作、希望减少接口类型的重复维护 |
这些是选型倾向,不是硬性限制。GraphQL 也能用于 TypeScript 全栈,tRPC 也能服务多个客户端。按需返回字段不等于查询一定更快,类型共享也不等于运行时一定安全。两者都需要根据业务做好鉴权、输入校验和查询性能控制。
# 面试时怎么解释 GraphQL 和 tRPC 的区别
建议回答:它们都能用于前后端交互,但关注点不同。GraphQL 让前端在后端定义的范围内选择需要的字段,适合不同客户端有不同数据需求的场景。tRPC 则让 TypeScript 前端直接获得后端接口的参数和返回值类型,调用起来像调用函数,减少手动维护类型的工作。tRPC 不依赖 GraphQL,也不会自动按前端选择裁剪返回字段。无论选哪个,权限检查和输入校验都仍然要在后端做。
项目选型回答见 Web3 大学:为什么选择 tRPC,而不是 GraphQL。GraphQL 的 Schema、Resolver 与关联查询说明见 GraphQL 基础。
官方参考:tRPC 介绍 (opens new window)、tRPC 入门与类型共享 (opens new window)、以太坊 JSON-RPC 接口 (opens new window)。