原文作者:Ikeh Akinyemion
原文地址:blog.logrocket.com/build-full-…
翻译:一川
写在前面
本文最后由 Ikeh Akinyemion 于 2023 年 5 月 26 日更新,概述了四种 API 架构风格:RPC、REST、GraphQL 和 tRPC。
你可能已经熟悉远程过程调用框架 gRPC。鉴于命名的相似性,您可能倾向于认为 tRPC 与它有某种关系,或者做相同或类似的事情。然而,事实并非如此。
虽然 tRPC 实际上也是一个远程过程调用框架,但其目标和基础与 gRPC 有着根本的不同。tRPC 的主要目标是提供一种简单、类型安全的方式来构建基于 TypeScript 和 JavaScript 的项目的 API,占用空间最小。
在本文中,我们将使用 tRPC 构建一个简单的全栈 TypeScript 应用,该应用在代码和跨 API 边界时是类型安全的。我们将构建一个以猫为主题的小型应用程序,以展示如何在后端设置 tRPC 以及如何在 React 前端中使用创建的 API。您可以在 GitHub(github.com/zupzup/trpc…) 上找到此示例的完整代码。让我们开始吧!
RPC、REST、GraphQL 和 tRPC 概述
在构建全栈应用程序时,开发人员需要考虑的一个关键方面是前端和后端的通信方式。这种相互作用是通过 API 促进的,并且有几种架构样式可供选择。在这里,我们将概述四种:RPC,REST,GraphQL和tRPC。
RPC(远程过程调用)
RPC 是一种协议,一个程序可以使用该协议从位于网络中另一台计算机上的程序请求服务,而无需了解网络的详细信息。这就像调用另一台机器上的函数一样。RPC 有许多实现,包括 gRPC、XML-RPC 和 tRPC,但总体概念保持不变:在远程计算机上调用过程。
REST(具象状态转移)
REST 是一种驱动 Web 的架构风格,并且一直是设计 Web API 的标准。在 RESTful 系统中,资源由 URL 标识,并使用 HTTP 方法(如 GET、POST、PUT、DELETE 等)进行访问和操作。REST提倡无状态通信,这意味着每个HTTP请求都是独立发生的,不需要知道以前发生过的任何请求。
GraphQL
GraphQL 是一种用于 API 的查询语言,也是使用现有数据执行这些查询的运行时。它允许客户端定义所需数据的结构,然后服务器精确地返回客户端请求的数据。这可以使其比 RESTful API 更有效,后者由服务器定义返回哪些数据。
tRPC
tRPC 是一种创新的远程过程调用 (RPC) 框架,专门设计用于利用 TypeScript 的强大推理来派生 API 路由器的类型定义。这允许开发人员直接从前端调用具有完整类型安全性和自动完成功能的 API 过程,从而提供更加集成和高效的开发体验。
探索 tRPC
如果你有一个在后端和前端都使用 TypeScript 的应用程序,tRPC 可以帮助你以一种在依赖项和运行时复杂性方面产生绝对最小开销的方式设置 API。但是,tRPC 仍然提供类型安全及其附带的所有功能,例如整个 API 的自动完成以及以无效方式使用 API 时的错误。
实际上,您可以将 tRPC 视为 GraphQL 的非常轻量级的替代方案。但是,tRPC 并非没有局限性。首先,它仅限于TypeScript和JavaScript。此外,您正在构建的 API 将遵循 tRPC 模型,这意味着它不会是 REST API。不能简单地将 REST API 转换为 tRPC,并具有与以前相同的 API,但包含类型。
从本质上讲,tRPC 是一个包含电池的解决方案,可以满足您的所有 API 需求,但它也将是一个 tRPC-API
。这就是名称中的 RPC 的来源,从根本上改变了远程调用的工作方式。tRPC 可能是一个很好的解决方案,只要您习惯在 API 网关上使用 TypeScript
。
设置 tRPC
让我们首先在项目根目录中创建一个名为backend
的文件夹。在 backend
文件夹中,我们将创建一个package.json
文件,如下所示:
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"start-server": "./node_modules/.bin/tsc && node build/index.js"
},
"dependencies": {
"@trpc/server": "^10.29.1",
"cors": "^2.8.5",
"express": "^4.18.2",
"zod": "^3.21.4"
},
"devDependencies": {
"@tsconfig/node14": "^1.0.1",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"typescript": "^4.5"
},
"keywords": [],
"author": "",
"license": "ISC"
}
我们还将创建一个tsconfig.json
文件:
{
"extends": "@tsconfig/node14/tsconfig.json",
"compilerOptions": {
"outDir": "build"
}
}
接下来,我们将创建一个名为src
的文件夹,并在其中包含一个index.ts
文件。最后,我们将在backend
文件夹中执行 npm install
。这样,我们就完成了后端的设置。
对于前端,我们将使用创建 React App 在项目根目录中使用以下命令设置一个支持 TypeScript 的 React 应用程序:
$ npx create-react-app frontend --template typescript
我们还可以在npm install
文件夹中运行frontend
并使用npm start
运行应用程序,以查看一切正常且设置正确。接下来,我们将实现应用程序的后端。
设置我们的Express后端
安装依赖项
正如您在上面我们应用程序的 server
部分中的 package.json
中看到的那样,使用 Express
作为我们的 HTTP
服务器。此外,还添加了TypeScript
和trpc-server
依赖项。
除此之外,使用cors
库将 CORS 添加到 API,这对于此示例来说并不是必需的,但这是一个很好的做法。还添加了 Zod
,这是一个支持 TypeScript
的模式验证库,通常与 tRPC
结合使用。但是,您也可以使用其他库,如Yup
或 Superstruct
。稍后会确切地看到这是什么。
排除依赖项后,使用tRPC
支持设置基础的 Express
后端。
Express 后端
首先定义 tRPC
路由器,这是整个基础结构中非常重要的一部分,允许我们在类型安全和自动完成方面将后端和前端连接在一起。这个路由器应该在它自己的文件中,例如 router.ts
,因为我们稍后也会将其导入我们的React
应用程序中。
在 backend/src/router.ts
中,我们首先定义域对象 Cat 的数据结构:
import z from 'zod';
let cats: Cat[] = [];
const Cat = z.object({
id: z.number(),
name: z.string(),
});
const Cats = z.array(Cat);
...
export type Cat = z.infer<typeof Cat>;
export type Cats = z.infer<typeof Cats>;
你可能想知道为什么我们不构建简单的JavaScript
或TypeScript
类型和对象。由于我们使用 Zod
通过 tRPC
进行模式验证,因此还需要使用它构建这些域对象。实际上可以使用 Zod
添加验证规则,例如字符串的最大字符数、电子邮件验证等,将类型检查与实际验证相结合。
当输入无效时,还会收到自动创建的错误消息。但是,这些错误可以完全自定义。如果您对验证和错误处理感兴趣,请查看文档以获取更多信息。
在使用Zod
实现类型后,我们可以使用 z.infer
从中推断出 TypeScript
类型。一旦有了它,导出类型以在应用程序的其他部分(如前端)中使用,然后继续创建应用程序的核心。
使用以下router
定义更新 backend/src/router.ts
文件:
...
const trpcRouter = t.router({
get: t.procedure.input(z.number()).output(Cat).query((opts) => {
const { input } = opts;
const foundCat = cats.find((cat => cat.id === input));
if (!foundCat) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: `could not find cat with id ${input}`,
});
}
return foundCat;
}),
list: t.procedure.output(Cats).query(() => {
return cats;
}),
})
在上面的代码片段中,通过调用router()
方法并在其中定义不同的终结点来构造 tRPC
路由器。使用 tRPC
时,有两种主要类型的过程:
- 查询:用于获取数据。与 REST 中的 GET 方法相当
- 突变:用于修改数据。与 REST 中的 POST 、 PUT 、 PATCH 和 DELETE 方法相当
在这里,创建 query
个端点,一个用于按ID
检索单个 Cat
对象,另一个用于获取所有 Cat
对象。tRPC
还支持 infiniteQuery
的概念,如果需要,它使用游标返回可能无限数据的分页响应。
对于get
终结点,我们定义了一个 input
架构和 output
架构。此终结点本质上是一个 GET
操作,它根据给定的 ID
返回我们 Cat
对象的 JSON
。 input
是我们想要获取的 Cat
对象的 ID
。
在 query()
函数中,实现了实际的业务逻辑。例如,在实际应用程序中,可以调用服务或数据库层。但是,由于我们只是将 Cat 个对象存储在内存中(在本例中为数组),因此使用给定的 ID 搜索 Cat 。如果没有找到具有给定 ID 的 Cat ,则会抛出错误。如果找到 Cat ,返回它。
list
端点更简单,因为它不需要输入,只返回当前的 Cat 对象列表。它定义了一个 output
模式和一个返回所有 Cat 个对象的query()
函数。
将 .procedure
方法与链接的 .input
、 .output
和 .query
或 .mutation
方法一起使用,提供了一种清晰、类型安全的方式来定义请求和响应结构以及每个终结点的行为。
看看如何使用 tRPC 实现创建和删除:
...
const trpcRouter = t.router({
...
create: t.procedure
.input(
z.object({ name: z.string().max(50) }),
)
.mutation((opts) => {
const { input } = opts;
const newCat: Cat = { id: newId(), name: input.name };
cats.push(newCat)
return newCat
}),
delete: t.procedure.output(z.string()).input(z.object({ id: z.number() })).mutation((opts) => {
const { input } = opts;
cats = cats.filter(cat => cat.id !== input.id);
return "success"
})
})
function newId(): number {
return Math.floor(Math.random() * 10000)
}
...
export type TRPCRouter = typeof trpcRouter;
export default trpcRouter;
如您所见,使用.procedure.input().mutation()
方法来创建新的mutation
。
create mutation
需要具有 name 属性的输入对象,该属性是限制为最多 50 个字符的字符串。这由具有name: z.string().max(50)
属性的z.object()
调用处理。在 .mutation()
方法中,使用提供的 name 和随机生成的 id (由 newId 函数生成)创建一个新的 Cat 对象。然后将这个新的 Cat 对象添加到 cats 数组中并返回。
create mutation
将导致类似 POST /create
的东西,期待某种身体。
同样, delete
突变需要具有 id 属性(即数字)的输入对象。在 .mutation()
方法中,对 cats 数组进行过滤以删除具有提供的 id 的 Cat 对象,从而有效地从数组中删除 Cat 。此操作在完成后返回字符串“成功”。
这些响应实际上看起来并不像我们在这里定义的。相反,它们包装在 tRPC 响应中,如下所示:
{"id":null,"result":{"type":"data","data":"success"}}
这就是我们的route,我们拥有所需的所有端点。现在,我们必须将其与Express Web应用程序连接起来。创建一个./backend/src/index.ts
文件并添加以下内容:
import express, { Application } from 'express';
import cors from 'cors';
import * as trpcExpress from '@trpc/server/adapters/express';
import trpcRouter, { createContext } from './router';
const app: Application = express();
app.use(express.json());
app.use(cors());
app.use(
'/cat',
trpcExpress.createExpressMiddleware({
router: trpcRouter,
createContext,
}),
);
app.listen(8080, () => {
console.log("Server running on port 8080");
});
tRPC 附带一个用于 Express 的适配器,因此只需创建Express 应用程序并在应用程序内使用提供的 tRPC 中间件。可以定义一个应该使用此配置的子路由、一个路由器和一个上下文。
为每个传入请求调用上下文函数,并将其结果传递给处理程序。在上下文函数中,您可以为每个请求添加所需的上下文数据,例如身份验证令牌或登录用户的 userId
。
如果要了解有关使用 tRPC 进行授权的详细信息,文档(trpc.io/docs/server…
测试我们的Express后端
应用程序就是这样!让我们快速验证它,以便我们知道一切正常。
可以通过将目录更改为后端文件夹并执行 npm run 命令来启动应用程序:
$ npm run start-server
现在服务器正在运行,使用 cURL 发送一些 HTTP 请求。首先,让我们创建一个新的 Cat :
$ curl -X POST "http://localhost:8080/cat/create" -d '{"name": "Minka" }' -H 'content-type: application/json'
{"id":null,"result":{"type":"data","data":{"id":7216,"name":"Minka"}}}
然后,列出现有的 Cat 个对象:
$ curl "http://localhost:8080/cat/list"
{"id":null,"result":{"type":"data","data":[{"id":7216,"name":"Minka"}]}}
通过其 ID 获取 Cat :
$ curl "http://localhost:8080/cat/get?input=7216"
{"id":null,"result":{"type":"data","data":{"id":7216,"name":"Minka"}}}
最后,删除一个 Cat :
$ curl -X POST "http://localhost:8080/cat/delete" -d '{"id": 7216}' -H 'content-type: application/json'
{"id":null,"result":{"type":"data","data":"success"}}
$ curl "http://localhost:8080/cat/list"
{"id":null,"result":{"type":"data","data":[]}}
一切似乎都按预期工作。现在,有了后端,构建我们的 React 前端。
创建我们的 React 前端
首先,在client/src
文件夹中,创建一个 cats 文件夹以在我们的应用程序中添加一些结构。然后,我新 package.json
以反映其他依赖项:
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@tanstack/react-query": "^4.29.12",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@trpc/client": "^10.29.1",
"@trpc/react-query": "^10.29.1",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.34",
"@types/react-dom": "^18.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
在这个例子中,就像在官方的例子中一样,我们将使用 React Query,它为 React 应用程序添加了 API 交互。 添加 React Query 是完全可选的,可以只使用带有您选择的前端框架(包括 React)的香草客户端,并完全按照您想要的方式集成它。
从App.tsx 中构建应用程序的基本结构开始:
import { useState } from 'react';
import './App.css';
import { httpBatchLink } from '@trpc/client';
import type { TRPCRouter } from '../../backend/src/router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import Create from './cats/Create';
import Detail from './cats/Detail';
import List from './cats/List';
import { createTRPCReact } from '@trpc/react-query';
const BACKEND_URL = "http://localhost:8080/cat";
export const trpc = createTRPCReact<TRPCRouter>();
function App() {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: BACKEND_URL,
}),
],
}),
);
const [detailId, setDetailId] = useState(-1);
const setDetail = (id: number) => {
setDetailId(id);
}
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<div className="App">
<Create />
<List setDetail={setDetail}/>
{ detailId > 0 ? <Detail id={detailId} /> : null }
</div>
</QueryClientProvider>
</trpc.Provider>
);
}
export default App;
有很多东西需要解开,所以从顶部开始。用@trpc/react-query
中的 createTRPCReact
助手实例化 trpc
,并为其提供从后端应用导入的 TRPCRouter
。导出以在应用的其余部分使用。
从本质上讲,这会在下面创建指向 API 的所有绑定。接下来,创建一个React Query
客户端和一个 tRPC 客户端,为其提供后端的 URL。我们将使用此客户端向 API 发出请求,或者更确切地说,客户端 React Query
将在下面使用。
除了所有这些设置之外,还为 detailId 定义了一个状态变量,以便知道如果用户选择任何 1 个详细信息,要显示哪个 Cat 个详细信息。
如果查看从 App 返回的内容,你可以看到实际标记,带有 App 类的 div ,嵌套在两层中。这些层位于外部,即 tRPC 提供程序,内部是 React Query 提供程序。
这两个组件为整个应用提供了必要的运动部件。因此,可以在整个应用程序中使用 tRPC,并且查询调用与React 应用程序无缝集成。接下来,将向标记添加 Create 、 List 和 Detail 的组件,其中包括所有的业务逻辑。
从 Create 组件开始,在 src/cats 文件夹中创建一个 Create.css 和 Create.tsx 文件。在此组件中,将简单地创建一个表单,并将该表单连接到后端实现的 create 个突变。创建新的 Cat 对象后,希望重新获取包含 Cat 个对象的列表,以便它始终是最新的。
可以使用以下代码实现这一点:
import './Create.css';
import { ChangeEvent, useState } from 'react';
import { trpc } from '../App';
function Create() {
const [text, setText] = useState("");
const [error, setError] = useState("");
const cats = trpc.list.useQuery();
const createMutation = trpc.create.useMutation({
onSuccess: () => {
cats.refetch();
},
onError: (data) => {
setError(data.message);
}
});
const updateText = (event: ChangeEvent<HTMLInputElement>) => {
setText(event.target.value);
};
const handleCreate = async() => {
createMutation.mutate({ name: text });
setText("");
};
return (
<div className="Create">
{error && error}
<h2>Create Cat</h2>
<div>Name: <input type="text" onChange={updateText} value={text} /></div>
<div><button onClick={handleCreate}>Create</button></div>
</div>
);
}
export default Create;
从一些非常基础的、普通的 React 逻辑开始。为表单字段创建一些内部组件状态以及我们可能想要显示的潜在错误。我们返回一个简单的表单,其中包含一个连接到state的文本字段,以及一个提交它的按钮。
现在,看一下 handleCreate
函数。在 createMutation
上调用 .mutate
,在它上面定义,然后重置 text 字段。
createMutation
是使用 trpc.create.useMutation
个端点创建的。请注意,在 IDE 或编辑器中,键入 create 个 call 时,您将获得自动完成建议。还在有效负载中获得了 .mutate 调用的建议,建议我们使用 name 字段。
在.useMutation
调用中,定义了成功和错误时应该发生什么。如果我们遇到错误,只想使用组件内部状态显示它。如果成功创建了一个 Cat 对象,希望重新获取 Cat 个对象列表的数据。为此,定义了一个对端点 trpc.list.useQuery
的调用,并在 onSuccess
处理程序中调用了它。
已经可以看到将应用与 tRPC API 集成是多么容易,以及 tRPC 如何在开发过程中帮助我们。接下来看一下详细信息视图,在 cats
文件夹中创建Detail.tsx
和 Detail.css
:
import './Detail.css';
import { trpc } from '../App';
function Detail(props: {
id: number,
}) {
const cat = trpc.get.useQuery(props.id);
return (
cat.data ?
<div className="Detail">
<h2>Detail</h2>
<div>{cat.data.id}</div>
<div>{cat.data.name}</div>
</div> : <div className="Detail"></div>
);
}
export default Detail;
在上面的组件中,基本上使用.get.useQuery
来定义getCatById
端点,提供我们通过 props
从根组件获得的 ID。如果我们实际获得数据,我们渲染Cat
的细节。我们也可以在这里使用useEffects
进行数据获取。从本质上讲,任何将 API 与 React 应用程序集成的方式都可以与 tRPC 和 React Query 一起使用。
最后,通过创建 List.css
和 List.tsx
的 cats 来实现我们的List
组件。在 Cat 对象的列表中,我们将显示 Cat 的 ID 和名称,以及详细显示它的链接和删除它的链接:
import './List.css';
import { trpc } from '../App';
import type { Cat } from '../../../backend/src/router';
import { useState } from 'react';
function List(props: {
setDetail: (id: number) => void,
}) {
const [error, setError] = useState("");
const cats = trpc.list.useQuery();
const deleteMutation = trpc.delete.useMutation({
onSuccess: () => {
cats.refetch();
},
onError: (data) => {
setError(data.message);
}
});
const handleDelete = async(id: number) => {
deleteMutation.mutate({ id })
};
const catRow = (cat: Cat) => {
return (
<div key={cat.id}>
<span>
{cat.id}
</span>
<span>
{cat.name}
</span>
<span>
<a href="#" onClick={props.setDetail.bind(null, cat.id)}>detail</a>
</span>
<span>
<a href="#" onClick={handleDelete.bind(null, cat.id)}>delete</a>
</span>
</div>
);
};
return (
<div className="List">
<h2>Cats</h2>
<span>{error}</span>
{ cats.data && cats.data.map((cat) => {
return catRow(cat);
})}
</div>
);
}
export default List;
该组件基本上结合了在前两个组件中使用的功能。首先,使用 list.useQuery
端点获取 cat 列表, 实现 Cat 对象的删除操作,随后使用 deleteMutation
重新获取,指向后端的 delete mutation
。
除此之外,一切都非常相似。通过 props
从 App 传入 setDetailId
函数,以便可以将 cat 设置为在 Detail 中显示详细信息,并创建一个用于删除 cat 的处理程序,该处理程序执行mutation。
请注意 tRPC 提供的所有自动完成。如果键入错误的内容(例如终结点的名称),则会收到错误,并且在更正错误之前前端不会启动。我们的前端就是这样,让我们测试一下,看看 tRPC 的实际效果!
测试 tRPC 功能
首先,让我们从 npm start 开始应用,看看它是如何工作的。应用程序启动后,我们可以创建新猫,删除它们,并查看它们的详细信息页面,同时直接在列表中观察更改。它不是特别漂亮,但它有效:
让我们来看看 tRPC 如何在开发过程中提供帮助。假设想为Cat添加一个 age 字段:
const Cat = z.object({
id: z.number(),
name: z.string(),
age: z.number(),
});
...
const trpcRouter = t.router({
...
create: t.procedure
.input(
z.object({ name: z.string().max(50), age: z.number().min(1).max(30) }),
)
.mutation((opts) => {
const { input } = opts;
const newCat: Cat = { id: newId(), name: input.name, age: input.age };
cats.push(newCat)
return newCat
}),
...
})
...
将字段添加到域对象,还需要将其添加到 create 端点。在后端代码上点击保存后,在 ./frontend/src/cats/Create.tsx
中导航回前端代码。编辑器向我们显示了一个错误,因为在我们对 createMutation 的调用中缺少属性 age :
如果我们现在想将 age 字段添加到我们的mutation中,我们的编辑器将直接从更改的 router.ts 中,提供包含完整类型信息的自动完成:
虽然有一种简单的方法来在前端和后端创建 API 很好,但真正的卖点是,如果我在一侧而不是另一端进行重大更改,代码将无法构建。
例如,想象一个巨大的代码库,其中有多个团队在处理 API 端点和 UI 元素。在API兼容性方面具有这种安全性,并且几乎没有应用程序的开销,这是非常了不起的。
tRPC vs. GraphQL tRPC vs. GraphQL
为了比较 tRPC 和 GraphQL,让我们看几个关键因素,包括类型安全性、性能和简单性。
安全
tRPC 和 GraphQL 都提供安全性,这对于维护健壮的代码库、减少错误和提高开发人员的工作效率至关重要。但是,它们以不同的方式处理安全:
- tRPC:tRPC 通过 TypeScript 和 Zod 等库提供类型安全,用于模式验证,允许创建域对象。这些可以包括验证规则,从而将类型检查与实际验证相结合。这使得从后端到前端(包括 API 层)能够维护类型安全
- GraphQL:GraphQL 也支持类型安全,但它通过自己的类型系统来实现。定义 GraphQL 架构时,您可以指定可以查询的数据类型,然后使用这些类型来验证查询。这样可以更轻松地在开发期间及早发现错误
性能
tRPC 和 GraphQL 之间的性能比较可以有细微差别,因为它们取决于特定的用例、API 的设计和使用方式以及应用的特定优化:
- tRPC:tRPC 的目标是在依赖关系和运行时复杂性方面实现最小的开销,使其可能比 GraphQL 更轻量级。但是,特定的性能特征可能取决于各种因素,例如所处理数据的性质、代码的效率、网络条件等
- GraphQL:GraphQL 允许客户端准确请求他们需要的内容,这可以通过减少过度获取来有利于性能。但是,由于解析查询的复杂性,GraphQL 在服务器端可能更加资源密集,特别是如果它们是深度嵌套的或涉及实体之间的复杂关系
简单
简单性可能是主观的,取决于开发人员对所涉及的技术和概念的熟悉程度:
- tRPC:该框架旨在为熟悉 TypeScript 的开发人员提供简单直观的设计。但是,tRPC 的模型并不遵循 RESTful 原则,对于习惯于 RESTful API 的开发人员来说,这可能是一种范式转变。
- GraphQL:GraphQL 以其灵活性和表现力而闻名,允许客户准确指定他们需要的数据,这可以简化客户端数据处理。但是,由于其独特的查询语言以及管理模式和解析程序的需要,GraphQL 的学习曲线可能会更陡峭。
tRPC vs. REST tRPC 与 REST
tRPC 和 REST 提供了不同的方法来构建 API。tRPC 将 API 实现定义为协定,简化了 API,但需要对某些更改进行前端和后端重新生成。另一方面,REST 使用单独的协定来表示 API,因此只有在合约更改时才需要重建。
虽然 tRPC 将 API 构建为 RPC 调用并提供更简单的客户端 API,但它可能会失去非 TypeScript 或公共使用者的 REST API 的可预测性。REST 通过传统的 API 设计和 RPC 类型的客户端调用来维护系统的安全性和可预测性,而无需额外的抽象层。
tRPC vs. gRPC tRPC 与 gRPC
tRPC 和 gRPC 都提供类型安全的客户端/服务器 API,但在大多数方面操作不同。gRPC 利用 protobuf 进行紧凑的有线协议,其中模式在 proto 文件中定义,这 proto 个文件生成各种语言(C++、Java、Go、Python )的客户端/服务器。
另一方面,tRPC 采用一种独特的方法,直接在 TypeScript 中定义其架构,然后客户端和服务器都可以动态导入。它不像网络上的 gRPC 那样优化,因为它使用 HTTP,但使用起来要简单得多。与 gRPC 不同,tRPC 是一个仅限 TypeScript 的库,它是 Web 原生的——旨在在浏览器环境中有效运行。
使用 TypeScript 从 React Query 中useMutation
多个参数
在将 tRPC 与 React Query 和 TypeScript 一起使用的情况下,您可能会发现自己需要在 useMutation 中使用多个参数。这可以通过使用 JavaScript 对象来包装参数来实现。
例如,假设您的 tRPC 路由器中有一个登录过程,该过程接受单个参数 name 并返回一个用户对象。在组件中,可以使用 useMutation 调用此登录过程,传入具有 name 属性的对象。
下面是一个快速示例:
import { trpc } from '../utils/trpc';
export function MyComponent() {
// This can either be a tuple ['login'] or string 'login'
const mutation = trpc.login.useMutation();
const handleLogin = () => {
const name = 'logrocket blog';
const email = 'user@example.com';
mutation.mutate({ name, email });
};
return (
<div>
<h1>Login Form</h1>
<button onClick={handleLogin} disabled={mutation.isLoading}>
Login
</button>
{mutation.error && <p>Something went wrong! {mutation.error.message}</p>}
</div>
);
}
上面的代码片段设置了一个在 tRPC 中调用登录过程的突变。调用 handleLogin 函数时,它会触发将 name 和 email 参数包装在对象中的 mutation。
要传递多个参数,只需向传递给 mutation.mutate 的对象添加更多属性。例如,如果您的登录过程也需要密码,则可以调用 mutation.mutate({ name, age, password }) 。
写在最后
希望本文向您展示了在前端和后端使用 TypeScript 的情况下,tRPC 如何发挥作用。使用最少或没有额外依赖项,您可以专注于编译时正确性,而不是运行时检查。
显然,在某些情况下,TypeScript 的限制可能太大了,无法承受。tRPC 背后的原则在开发人员体验方面非常有用。tRPC是一个令人兴奋的项目,我将来肯定会关注它。这是此项目的 GitHub 存储库(github.com/Ikeh-Akinye…
Happy coding!
一川说
觉得文章不错的读者,不妨点个关注,收藏起来上班摸鱼的时候品尝。
欢迎关注笔者公众号「宇宙一码平川」,助你技术路上一码平川。