我是 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 入门 中有一些示例。