我是 Alex,GitHub 上的昵称是 "KATT",我想向大家介绍一个名为 tRPC 的库。我还没有发布过任何关于它的文章,所以我现在只是写这篇介绍来让它开始运作(但我们已经不知不觉地在 GitHub 上获得了超过 530 个 🌟)。期待文章和视频介绍的到来!如果你想随时了解最新消息或有任何问题,可以在 Twitter 上关注我 @alexdotjs。
简而言之 - tRPC 为你提供从你的(节点)服务器到你的客户端的端到端类型安全,甚至不需要声明类型。你在后端所做的只是在一个函数中返回数据,而在前端,你根据端点名称使用该数据。
以下是执行 tRPC 端点和客户端调用时的示例:
我为 React 创建了一个库(@trpc/react
),它建立在优秀的 react-query 之上,但客户端库(@trpc/client
)可以在没有 React 的情况下工作(如果你想构建一个特定的 Svelte/Vue/Angular/[..]库,请联系我!
不需要代码生成,你可以很容易地将其添加到现有的 Next.js/CRA/Express 项目中。
示例
以下是一个名为 hello
的 tRPC 过程(也称为端点)的示例,它接受一个 string
参数。
tsx
const appRouter = trpc.router().query('hello', {input: z.string().optional(),resolve: ({ input }) => {return {text: `hello ${input ?? 'world'}`,};},});export type AppRouter = typeof appRouter;
tsx
const appRouter = trpc.router().query('hello', {input: z.string().optional(),resolve: ({ input }) => {return {text: `hello ${input ?? 'world'}`,};},});export type AppRouter = typeof appRouter;
以下是一个使用该数据的类型安全的客户端
tsx
import type { AppRouter } from './server';async function main() {const client = createTRPCClient<AppRouter>({url: `http://localhost:2022`,});const result = await client.query('hello', '@alexdotjs');console.log(result); // --> { text: "hello @alexdotjs" }}main();
tsx
import type { AppRouter } from './server';async function main() {const client = createTRPCClient<AppRouter>({url: `http://localhost:2022`,});const result = await client.query('hello', '@alexdotjs');console.log(result); // --> { text: "hello @alexdotjs" }}main();
这就是你需要获得类型安全性的全部内容!result
的类型是从后端在函数中返回的内容推断出来的。输入数据的类型也从验证器的返回值推断出来,因此数据可以直接安全使用 - 实际上,你必须通过验证器传递输入数据(tRPC 与 zod/yup/自定义验证器开箱即用)。
以下是一个 CodeSandbox 链接,你可以在其中使用上面的示例进行操作:https://githubbox.com/trpc/trpc/tree/next/examples/standalone-server(查看终端输出,而不是预览!)
什么?我从后端导入代码到客户端? - 不,你实际上没有
尽管看起来像是这样,但实际上没有代码从服务器共享到客户端;TypeScript 的 import type
"[..]只导入声明以用于类型注释和声明。它总是被完全擦除,因此在运行时没有它的残留。" - 这是 TypeScript 3.8 中添加的功能 - 查看 TypeScript 文档。
不需要代码生成,只要你能够从服务器共享类型到客户端(希望你已经在使用单仓库了),你就可以在今天将它添加到你的应用程序中。
但我们才刚刚开始!
我之前提到过有一个 React 库,在 React 中使用上面数据的做法是
tsx
const { data } = trpc.useQuery(['hello', '@alexdotjs']);
tsx
const { data } = trpc.useQuery(['hello', '@alexdotjs']);
...你将在客户端获得类型安全的数据。
你可以在今天将 tRPC 添加到现有的遗留项目中(为 Express/Next.js 提供了适配器),它可以与 CRA 很好地配合使用,也应该可以与 React Native 配合使用。它甚至不与 React 绑定,所以如果你想做一个 Svelte 或 Vue 库,请联系我。
如何处理数据变动?
变动与查询一样简单,实际上它们在底层是相同的,只是作为语法糖以不同的方式暴露出来,并产生 HTTP POST 请求而不是 GET 请求。
以下是一个更复杂的示例,它使用数据库,取自我们在 todomvc.trpc.io 上的 TodoMVC 示例 / https://github.com/trpc/trpc/tree/next/examples/next-prisma-todomvc
tsx
const todoRouter = createRouter().mutation('add', {input: z.object({id: z.string().uuid(),data: z.object({completed: z.boolean().optional(),text: z.string().min(1).optional(),}),}),async resolve({ ctx, input }) {const { id, data } = input;const todo = await ctx.task.update({where: { id },data,});return todo;},});
tsx
const todoRouter = createRouter().mutation('add', {input: z.object({id: z.string().uuid(),data: z.object({completed: z.boolean().optional(),text: z.string().min(1).optional(),}),}),async resolve({ ctx, input }) {const { id, data } = input;const todo = await ctx.task.update({where: { id },data,});return todo;},});
React 使用看起来像这样
tsx
const addTask = trpc.useMutation('todos.add');return (<><inputplaceholder="What needs to be done?"onKeyDown={(e) => {const text = e.currentTarget.value.trim();if (e.key === 'Enter' && text) {addTask.mutate({ text });e.currentTarget.value = '';}}}/></>)
tsx
const addTask = trpc.useMutation('todos.add');return (<><inputplaceholder="What needs to be done?"onKeyDown={(e) => {const text = e.currentTarget.value.trim();if (e.key === 'Enter' && text) {addTask.mutate({ text });e.currentTarget.value = '';}}}/></>)
暂时结束。
总之,正如我所说,我只是想让它开始运作。还有很多事情
- 为传入请求创建上下文,以获取用户特定数据,这些数据被依赖注入到解析器中 - 链接
- 对路由器的中间件支持 - 链接
- 合并路由器(你可能不希望将所有后端数据放在一个文件中) - 链接
- 在 React 世界中,你见过的最简单的服务器端渲染,使用我们的
@trpc/next
适配器 - 链接 - 类型安全的错误格式 - 链接
- 数据转换器(在网络上使用 Date/Map/Set 对象) - 链接
- React Query 的助手
如果你想开始使用,在 Next.js 入门 中有一些示例。