跳至主内容

宣布 tRPC v11

· 1 分钟阅读
tRPC
tRPC 团队
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

尽管通过 @next 标签发布的 tRPC v11 早已具备生产就绪状态,但我们一直沉迷于添加新功能而没有严格遵守语义化版本规范。今天,我们激动地宣布,终于要正式发布 tRPC v11 了!

自 2022 年 11 月我们发布上一个主要版本以来,tRPC 社区取得了显著增长:

随着 tRPC v11 的发布,我们很高兴地告诉大家,v11 版本已经通过我们的 @next 频道稳定演进,并被许多大型 TypeScript 项目用于生产环境

对于新项目,你可以通过示例应用快速上手并学习 tRPC v11。对于仍在使用 tRPC v10 的项目,请访问 v11 迁移指南

变更概览

v11 与 v10 基本保持向后兼容,但它带来了许多新功能和改进。以下是一些亮点:

支持 TanStack Query v5

TanStack Query v5 发布时,它要求 tRPC 的 react-query 集成进行一些破坏性变更。这项支持很早就通过 @next 标签提供了,但现在已正式发布。许多项目已经选择升级,并享受到了完整的 React Suspense 支持以及其他诸多改进带来的好处。关于迁移 tRPC React 客户端代码的建议,你可以参考 TanStack Query 的迁移指南

全新的 TanStack React Query 集成

查看博客文章

支持 FormData / 非 JSON 内容类型

我们最常被要求的功能之一就是能够发送和接收不仅仅是 JSON 数据。借助 tRPC v11,你现在可以发送和接收多种非 JSON 内容类型FormData 以及二进制类型(如 BlobFileUint8Array)。你可以在此处找到使用这些内容类型的示例。

ts
import { publicProcedure, router } from './trpc';
import { octetInputParser } from '@trpc/server/http';
import { z } from 'zod';
 
const appRouter = router({
formData: publicProcedure
.input(z.instanceof(FormData))
.mutation(async ({ input }) => {
(parameter) input: FormData
}),
file: publicProcedure
.input(octetInputParser)
.mutation(async ({ input }) => {
(parameter) input: ReadableStream<any>
}),
});
ts
import { publicProcedure, router } from './trpc';
import { octetInputParser } from '@trpc/server/http';
import { z } from 'zod';
 
const appRouter = router({
formData: publicProcedure
.input(z.instanceof(FormData))
.mutation(async ({ input }) => {
(parameter) input: FormData
}),
file: publicProcedure
.input(octetInputParser)
.mutation(async ({ input }) => {
(parameter) input: ReadableStream<any>
}),
});

React Server Components / Next.js App Router

虽然从一开始你就可以通过以下任一方式将 tRPC 与 Next.js App Router 结合使用:

但两者之间的衔接仍存在粗糙之处。当需要维护不同的数据获取模式时,缓存失效模式变得混杂,导致开发体验不尽如人意,未能达到 tRPC 的标准水准。

为此,我们加强了对 React 服务器组件(RSC)的支持,并新增预取助手函数,使您能更便捷地结合纯服务端运行的 RSC 能力与 React Query 高度动态的客户端缓存。 现在您可以在 RSC 中启动过程执行,在客户端获取待处理的 Promise,并自动向 React Query 缓存注水。这让您能够构建高度动态的应用,同时避免服务端-客户端瀑布流问题。更多细节请参阅服务器组件文档

除了这些新的预取模式,我们还新增了对服务器函数的实验性支持。您可以在博客文章中了解详情。随着服务器函数在生态中逐渐成为成熟模式,我们将持续迭代此功能。

我们还与 TanStack 团队协作设计了其服务器函数的 API。我们的目标是将 tRPC 强大的中间件系统拆分到独立包中,使其可在整个生态系统中复用。

查询和变更的流式响应

我们推出了 httpBatchStreamLink,支持从查询流式传输响应。这在处理大型数据集或需要实时处理数据并逐步分发到前端时非常有用。该功能并非用于替代订阅,而是为您工具箱增添的又一利器。

ts
// @filename: server.ts
import { publicProcedure, router } from './trpc';
 
const appRouter = router({
examples: {
iterable: publicProcedure.query(async function* () {
let i = 0;
while (true) {
await new Promise((resolve) => setTimeout(resolve, 500));
yield i++;
}
}),
},
});
 
export type AppRouter = typeof appRouter;
 
 
// @filename: client.ts
import { createTRPCClient, httpBatchStreamLink } from '@trpc/client';
import type { AppRouter } from './server';
 
const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchStreamLink({
url: 'http://localhost:3000',
}),
],
});
const iterable = await trpc.examples.iterable.query();
const iterable: AsyncIterable<number, never, unknown>
 
for await (const value of iterable) {
console.log('Iterable:', value);
const value: number
}
ts
// @filename: server.ts
import { publicProcedure, router } from './trpc';
 
const appRouter = router({
examples: {
iterable: publicProcedure.query(async function* () {
let i = 0;
while (true) {
await new Promise((resolve) => setTimeout(resolve, 500));
yield i++;
}
}),
},
});
 
export type AppRouter = typeof appRouter;
 
 
// @filename: client.ts
import { createTRPCClient, httpBatchStreamLink } from '@trpc/client';
import type { AppRouter } from './server';
 
const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchStreamLink({
url: 'http://localhost:3000',
}),
],
});
const iterable = await trpc.examples.iterable.query();
const iterable: AsyncIterable<number, never, unknown>
 
for await (const value of iterable) {
console.log('Iterable:', value);
const value: number
}

路由定义简写语法

我们引入了新的路由简写语法,简化路由定义过程。文档

ts
const appRouter = router({
// Shorthand plain object for creating a sub-router
nested1: {
proc: publicProcedure.query(() => '...'),
},
// Equivalent of:
nested2: router({
proc: publicProcedure.query(() => '...'),
}),
});
ts
const appRouter = router({
// Shorthand plain object for creating a sub-router
nested1: {
proc: publicProcedure.query(() => '...'),
},
// Equivalent of:
nested2: router({
proc: publicProcedure.query(() => '...'),
}),
});

订阅功能:服务器发送事件及其他改进

  • tRPC v11 引入了使用服务器发送事件(SSE)处理订阅的新方式。这是在不依赖复杂 WebSocket 的情况下实现应用实时更新的理想方案。我们建议优先采用此方案。

  • 我们新增了对 JavaScript 生成器在订阅中的应用支持。这让您能以更符合 JavaScript 原生特性的方式编写产出多值订阅处理器,并在完成后自动清理资源。

  • 订阅功能现支持输出验证,增强了订阅处理器的类型安全性。

告别 v9 的 .interop() 模式

在 tRPC v10 中,我们引入了 .interop() 模式为 v9 用户提供平滑迁移路径。tRPC v11 已移除 .interop() 模式。若您仍在使用 .interop() 模式,请参考 v10 迁移指南完成向现行 API 的过渡。

迁移至 v11

若您当前正在使用 tRPC v10,请遵循迁移指南升级至 v11。该指南涵盖了 v11 的所有破坏性变更和新功能。

致谢!

tRPC 核心团队全体成员衷心感谢您对 tRPC 的使用与支持。