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 的类型提示是怎么来的

以查询课程为例,一次调用可以分成四步:

  1. 后端定义课程查询接口,规定输入的 id 是字符串,返回课程标题等字段,并导出整套接口的 AppRouter 类型。
  2. 前端通过 import type 引用这个类型,创建 tRPC 客户端,因此知道有哪些接口、每个接口接受和返回什么数据。共享的是类型,不是把数据库代码放进浏览器。
  3. 前端调用课程查询方法时,客户端仍然会发送网络请求,由后端查询数据库并返回结果,不是在浏览器里直接执行后端函数。
  4. 如果把 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
1
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)。

上次更新时间: 2026年10月10日 06:39:50