一、tRPC 简介
tRPC(TypeScript 远程过程调用)是 RPC 的一种实现,专为 TypeScript monorepos 设计。它有自己的风格,但其核心是 RPC。
二、程序 Procedure
API 端口,可以多种 查询/突变/订阅。用于创建后端函数 创建后端函数。
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;
三、查询 Query
与 GraphQL 的 query 设计极为相似,直接获取数据的 Procedure。在服务端 publicProcedure 具备 query 函数,query 函数接受一个函数作为参数,函数参数的返回值是客户端返回值。
const appRouter = router({
hello: publicProcedure.query(() => {
return {
message: 'hello world',
};
}),
});
四、突变 Mutation
与 GraphQL 的 mutation 设计极为相似,创建、更新或删除某些数据的Procedure。在服务端 publicProcedure 具备 mutation 函数,mutation 函数接受一个函数作为参数,函数参数的返回值中使用 options 获取上下文,用于可变操作。
const appRouter = router({
goodbye: publicProcedure.mutation(async (opts) => {
await opts.ctx.signGuestBook();
return {
message: 'goodbye!',
};
}),
});
五、订阅 Subscription
与 GraphQL 的 subscription 设计极为相似,创建持久连接并侦听更改的Procedure。在 nodejs 服务端通常配合 事件触发器 和 可观察对象 observable一起使用。
const ee = new EventEmitter();
const t = initTRPC.create();
export const appRouter = t.router({
onAdd: t.procedure.subscription(() => {
return observable<Post>((emit) => {
const onAdd = (data: Post) => {
emit.next(data);
};
// trigger `onAdd()` when `add` is triggered in our event emitter
ee.on('add', onAdd);
// unsubscribe function when client disconnects or stops subscribing
return () => {
ee.off('add', onAdd);
};
});
}),
});
ee.on 在监听 add 事件,如果使用 ee.emit 触发 add 事件,可观察对象的中的函数 onAdd 就会触发调用 next 方法。
六、路由 Router
路由是一个集合,是 procedure 命令空间下的集合。
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;
const appRouter = router({
greeting: publicProcedure.query(() => 'hello tRPC v10!'),
});
在 router 函数中定义路由对象,路由可以合并,合并的方式有两种:
- 直接子 router 函数上
- 使用 mergeRouters 工具函数
七、上下文 Context
通常用于会话状态、身份认证和数据库连接等内容。Context 使用分为
- 创建上下文
const createContext = async () => {
//
}
const t = initTRPC.context<typeof createContext>().create();
- 使用上下文
t.procedure.use(({ ctx }) => { ... });
- 定义 http 服务式使用
const handler = createHTTPHandler({
router: appRouter,
createContext,
});
- 获取 caller 时使用
const caller = appRouter.createCaller(await createContext());
这里的 caller 可以直接调用 appRouter 内部的方法来获取数据。
- 服务端 helpers 函数时使用
const helpers = createServerSideHelpers({
router: appRouter,
ctx: await createContext(),
});
八、中间件 Middleware
中间件可以在 procedure 运行之前和之后运行,在中间件中可以修改上往下文中的数据
const t = initTRPC.context<Context>().create();
export const middleware = t.middleware;
const mid = middleware(async (opts) => {
// 中间件逻辑
return opts.next({
ctx: {
value: 'new value' as const,
},
});
});
export const midProcedure = publicProcedure.use(mid);
middleware 接受一个函数作为对象,通过 opts 获取上下文,并且可以调用可观察对象的 next 方法。
九、验证 Validation
数据验证是一个重要的部分,在router 中 publicProcedure 函数具有 input/output 方法用于验证。
import { z } from 'zod';
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
).output(
z.object({
greeting: z.string(),
}),
)
.query(),
});
input 的 validate 推荐使用 Zod 库进行校验。
十、基于 Remix 的使用示例
Remix 是一个全栈框架,配合 tRPC 获取完整的类型提示,使得 Remix 变得非常好用。
10.1)初始化并安装依赖
npx create-remix@latest
cd your app_dir
pnpm add zod @trpc/server
10.1)定义一个辅助函数: utils.ts 用于初始化 tRPC
import { initTRPC } from "@trpc/server";
export const t = initTRPC.create();
10.3)定义 user 路由
import { z } from 'zod';
import { t } from '../utils';
export default t.router({
create: t.procedure
.input(
z.object({
email: z.string().email(),
password: z.string().min(8),
})
)
.mutation(async ({ ctx, input }) => {
// console.log("xxx", ctx, input)
return { ...input }
}),
login: t.procedure
.input(
z.object({
email: z.string().email(),
password: z.string().min(8),
})
)
.mutation(async ({ ctx, input }) => {
//
}),
})
10.5)定义 app Router
import { t } from '../utils';
import userRoute from '../router/users.route'
export const appRouter = t.router({
users: userRoute
});
// export type definition of API
export type AppRouter = typeof appRouter;
10.6)在 Remix loader 函数使用
export const loader = async () => {
const caller = appRouter.createCaller({})
const u = await caller.users.create({ email: 'a@t.com', password: 'rtdfgtre'})
return {
title: "Remix App",
...u
};
}
10.7)项目地址
remix-trpc 一个基于 Remix + tRPC + Primsa 的基础项目。
十一、客户端使用
不同的客户端,不同的框架 tRPC 对齐进行不同的支持。本文以原生 JS 为例,进行使用:
安装依赖
pnpm add @trpc/client @trpc/server
定义 tRPC 客户端
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../path/to/server/trpc';
const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
}),
],
});
使用客户端
const bilbo = await client.aa.query('xx');
const frodo = await client.bb.mutate({ name: 'zz' });
//
当然 tRPC 对主流的 React 框架, Next.js 框架都做了支持。感兴趣可以自己尝试一下。
十二、小结
tRPC 是基于 TS 的远程调用协议的实现,同时在其中包含了 GraphQL 处理接口的方式。tRPC 路由十分强大, 具有强大的组合能力。同时 tRPC 在不同的客户端得到各种支持。本文重在 tRPC 的八个核心概念,有了理论基础能更好的深入技术栈。