Skip to main content
Version: 10.x

从 v9 迁移到 v10

欢迎使用 tRPC v10!我们很高兴为你带来一个新的主要版本,以继续通过出色的 DX 实现完美的端到端类型安全性。

¥Welcome to tRPC v10! We're excited to bring you a new major version to continue the journey towards perfect end-to-end type safety with excellent DX.

在版本 10 的框架下,我们正在解锁性能改进,为你带来生活质量的增强,并为我们在未来构建新功能创造空间。

¥Under the hood of version 10, we are unlocking performance improvements, bringing you quality of life enhancements, and creating room for us to build new features in the future.

tRPC v10 为来自 v9 的用户提供了一个兼容层。.interop() 允许你逐步采用 v10,以便你可以继续构建项目的其余部分,同时仍然享受 v10 的新功能。

¥tRPC v10 features a compatibility layer for users coming from v9. .interop() allows you to incrementally adopt v10 so that you can continue building the rest of your project while still enjoying v10's new features.

变更摘要

¥Summary of changes

Initializing your server
/src/server/trpc.ts
ts
/**
* This is your entry point to setup the root configuration for tRPC on the server.
* - `initTRPC` should only be used once per app.
* - We export only the functionality that we use so we can enforce which base procedures should be used
* * Learn how to create protected base procedures and other things below:
* @see https://trpc.nodejs.cn/docs/v10/router
* @see https://trpc.nodejs.cn/docs/v10/procedures
*/
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
import { Context } from './context';
const t = initTRPC.context<Context>().create({
/**
* @see https://trpc.nodejs.cn/docs/v10/data-transformers
*/
transformer: superjson,
/**
* @see https://trpc.nodejs.cn/docs/v10/error-formatting
*/
errorFormatter(opts) {
return opts.shape;
},
});
/**
* Create a router
* @see https://trpc.nodejs.cn/docs/v10/router
*/
export const router = t.router;
/**
* Create an unprotected procedure
* @see https://trpc.nodejs.cn/docs/v10/procedures
**/
export const publicProcedure = t.procedure;
/**
* @see https://trpc.nodejs.cn/docs/v10/merging-routers
*/
export const mergeRouters = t.mergeRouters;
/src/server/trpc.ts
ts
/**
* This is your entry point to setup the root configuration for tRPC on the server.
* - `initTRPC` should only be used once per app.
* - We export only the functionality that we use so we can enforce which base procedures should be used
* * Learn how to create protected base procedures and other things below:
* @see https://trpc.nodejs.cn/docs/v10/router
* @see https://trpc.nodejs.cn/docs/v10/procedures
*/
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
import { Context } from './context';
const t = initTRPC.context<Context>().create({
/**
* @see https://trpc.nodejs.cn/docs/v10/data-transformers
*/
transformer: superjson,
/**
* @see https://trpc.nodejs.cn/docs/v10/error-formatting
*/
errorFormatter(opts) {
return opts.shape;
},
});
/**
* Create a router
* @see https://trpc.nodejs.cn/docs/v10/router
*/
export const router = t.router;
/**
* Create an unprotected procedure
* @see https://trpc.nodejs.cn/docs/v10/procedures
**/
export const publicProcedure = t.procedure;
/**
* @see https://trpc.nodejs.cn/docs/v10/merging-routers
*/
export const mergeRouters = t.mergeRouters;
Defining routers & procedures
ts
// v9:
const appRouter = trpc.router()
.query('greeting', {
input: z.string(),
resolve(opts) {
return `hello ${opts.input}!`;
},
});
// v10:
const appRouter = router({
greeting: publicProcedure
.input(z.string())
.query((opts) => `hello ${opts.input}!`),
});
ts
// v9:
const appRouter = trpc.router()
.query('greeting', {
input: z.string(),
resolve(opts) {
return `hello ${opts.input}!`;
},
});
// v10:
const appRouter = router({
greeting: publicProcedure
.input(z.string())
.query((opts) => `hello ${opts.input}!`),
});
Calling procedures
ts
// v9
client.query('greeting', 'KATT');
trpc.useQuery(['greeting', 'KATT']);
// v10
// You can now CMD+click `greeting` to jump straight to your server code.
client.greeting.query('KATT');
trpc.greeting.useQuery('KATT');
ts
// v9
client.query('greeting', 'KATT');
trpc.useQuery(['greeting', 'KATT']);
// v10
// You can now CMD+click `greeting` to jump straight to your server code.
client.greeting.query('KATT');
trpc.greeting.useQuery('KATT');
Inferring types

v9

ts
// Building multiple complex helper types yourself. Yuck!
export type TQuery = keyof AppRouter['_def']['queries'];
export type InferQueryInput<TRouteKey extends TQuery> = inferProcedureInput<
AppRouter['_def']['queries'][TRouteKey]
>;
type GreetingInput = InferQueryInput<'greeting'>;
ts
// Building multiple complex helper types yourself. Yuck!
export type TQuery = keyof AppRouter['_def']['queries'];
export type InferQueryInput<TRouteKey extends TQuery> = inferProcedureInput<
AppRouter['_def']['queries'][TRouteKey]
>;
type GreetingInput = InferQueryInput<'greeting'>;

v10

Inference helpers

Twoslash failure

Errors were thrown in the sample, but not included in an errors tag

These errors were not marked as being expected: 2307 7006.
Expected: // @errors: 2307 7006

Compiler Errors:

server.ts
[2307] 25 - Cannot find module '@trpc/server' or its corresponding type declarations.
[7006] 206 - Parameter 'opts' implicitly has an 'any' type.
[7006] 413 - Parameter 'opts' implicitly has an 'any' type.

index.ts
[2307] 125 - Cannot find module '@trpc/server' or its corresponding type declarations.

Raising Code:

// @module: esnext
// @include: server
// @filename: index.ts
// ---cut---
// Inference helpers are now shipped out of the box.
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server';

type RouterInput = inferRouterInputs<AppRouter>;
type RouterOutput = inferRouterOutputs<AppRouter>;

type PostCreateInput = RouterInput['post']['create'];
//   ^?
type PostCreateOutput = RouterOutput['post']['create'];
//   ^?

See Inferring types for more.

Middlewares

Middlewares are now reusable and can be chained, see the middleware docs for more.

ts
// v9
const appRouter = trpc
.router()
.middleware((opts) => {
const { ctx } = opts;
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...ctx,
user: ctx.user,
},
});
})
.query('greeting', {
resolve(opts) {
return `hello ${opts.ctx.user.name}!`;
},
});
// v10
const protectedProcedure = t.procedure.use((opts) => {
const { ctx } = opts;
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
// Old context will automatically be spread.
// Only modify what's changed.
user: ctx.user,
},
});
});
const appRouter = t.router({
greeting: protectedProcedure.query((opts) => {
return `Hello ${opts.ctx.user.name}!`
}),
});
ts
// v9
const appRouter = trpc
.router()
.middleware((opts) => {
const { ctx } = opts;
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
...ctx,
user: ctx.user,
},
});
})
.query('greeting', {
resolve(opts) {
return `hello ${opts.ctx.user.name}!`;
},
});
// v10
const protectedProcedure = t.procedure.use((opts) => {
const { ctx } = opts;
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
// Old context will automatically be spread.
// Only modify what's changed.
user: ctx.user,
},
});
});
const appRouter = t.router({
greeting: protectedProcedure.query((opts) => {
return `Hello ${opts.ctx.user.name}!`
}),
});
Full example with data transformer, OpenAPI metadata, and error formatter
/src/server/trpc.ts
ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
// Context is usually inferred,
// but we will need it here for this example.
interface Context {
user?: {
id: string;
name: string;
};
}
interface Meta {
openapi: {
enabled: boolean;
method: string;
path: string;
};
}
export const t = initTRPC
.context<Context>()
.meta<Meta>()
.create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
transformer: superjson,
});
/src/server/trpc.ts
ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
// Context is usually inferred,
// but we will need it here for this example.
interface Context {
user?: {
id: string;
name: string;
};
}
interface Meta {
openapi: {
enabled: boolean;
method: string;
path: string;
};
}
export const t = initTRPC
.context<Context>()
.meta<Meta>()
.create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
transformer: superjson,
});

从 v9 迁移

¥Migrating from v9

我们推荐两种策略来立即开始(和完成!)升级你的代码库。

¥We recommend two strategies to start (and finish!) upgrading your codebase today.

使用代码模式

¥Using a codemod

@sachinraja 为此重大升级创建了 一个优秀的 codemod。运行脚本即可在短时间内完成 95% 的工作。

¥@sachinraja has created an excellent codemod for this major upgrade. Run the script to have 95% of the work done for you in a matter of moments.

信息
  • 如果你使用 codemod,则在进行完整迁移之前,你仍应执行下面的步骤 1-3,以确保它适合你。

    ¥If you use the codemod, you should still do steps 1-3 below to make sure that works for you before doing the full migration.

  • 请注意,这个代码模块并不完美,但会为你完成很多繁重的工作。

    ¥Please note that this codemod isn't perfect but will do a lot of the heavy lifting for you.

使用 .interop()

¥Using .interop()

今天重写所有现有的 v9 路由对于你和你的团队来说可能负担太重。相反,让我们保留这些 v9 程序,并通过利用 v10 的 interop() 方法逐步采用 v10。

¥Rewriting all of your existing v9 routes today may be too heavy of a lift for you and your team. Instead, let's keep those v9 procedures in place and incrementally adopt v10 by leveraging v10's interop() method.

1. 在 v9 路由上启用 interop()

¥ Enable interop() on your v9 router

将你的 v9 路由变成 v10 路由只需要 10 个字符。将 .interop() 添加到 v9 路由的末尾...你的服务器代码就完成了!

¥Turning your v9 router into a v10 router only takes 10 characters. Add .interop() to the end of your v9 router... and you're done with your server code!

src/server/routers/_app.ts
diff
const appRouter = trpc
.router<Context>()
/* ... */
+ .interop();
export type AppRouter = typeof appRouter;
src/server/routers/_app.ts
diff
const appRouter = trpc
.router<Context>()
/* ... */
+ .interop();
export type AppRouter = typeof appRouter;
信息

.interop() 不支持的一些功能 个。我们期望几乎所有用户都能够使用 .interop() 在短短几分钟内迁移他们的服务器端代码。如果你发现 .interop() 无法正常工作,请务必执行 检查这里

¥There are a few features that are not supported by .interop(). We expect nearly all of our users to be able to use .interop() to migrate their server side code in only a few minutes. If you are discovering that .interop() is not working correctly for you, be sure to check here.

2. 创建 t 对象

¥ Create the t-object

现在,让我们初始化一个 v10 路由,以便我们可以开始使用 v10 来编写我们将要编写的任何新路由。

¥Now, let's initialize a v10 router so we can start using v10 for any new routes we will write.

src/server/trpc.ts
ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
import { Context } from './context';
const t = initTRPC.context<Context>().create({
// Optional:
transformer: superjson,
// Optional:
errorFormatter(opts) {
const { shape } = opts;
return {
...shape,
data: {
...shape.data,
},
};
},
});
/**
* We recommend only exporting the functionality that we
* use so we can enforce which base procedures should be used
**/
export const router = t.router;
export const mergeRouters = t.mergeRouters;
export const publicProcedure = t.procedure;
src/server/trpc.ts
ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
import { Context } from './context';
const t = initTRPC.context<Context>().create({
// Optional:
transformer: superjson,
// Optional:
errorFormatter(opts) {
const { shape } = opts;
return {
...shape,
data: {
...shape.data,
},
};
},
});
/**
* We recommend only exporting the functionality that we
* use so we can enforce which base procedures should be used
**/
export const router = t.router;
export const mergeRouters = t.mergeRouters;
export const publicProcedure = t.procedure;

3. 创建一个新的 appRouter

¥ Create a new appRouter

  1. 将旧的 appRouter 重命名为 legacyRouter

    ¥Rename your old appRouter to legacyRouter

  2. 创建一个新的应用路由:

    ¥Create a new app router:

Twoslash failure

Errors were thrown in the sample, but not included in an errors tag

These errors were not marked as being expected: 2307.
Expected: // @errors: 2307

Compiler Errors:

trpc.ts
[2307] 25 - Cannot find module '@trpc/server' or its corresponding type declarations.

_app.ts
[2307] 22 - Cannot find module '@trpc/server' or its corresponding type declarations.

Raising Code:

// @filename: trpc.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const mergeRouters = t.mergeRouters;
export const publicProcedure = t.procedure;
// @filename: _app.ts
import * as trpc from '@trpc/server';
// ---cut---
import { mergeRouters, publicProcedure, router } from './trpc';

// Renamed from `appRouter`
const legacyRouter = trpc
  .router()
  /* ... */
  .interop();

const mainRouter = router({
  greeting: publicProcedure.query(() => 'hello from tRPC v10!'),
});

// Merge v9 router with v10 router
export const appRouter = mergeRouters(legacyRouter, mainRouter);

export type AppRouter = typeof appRouter;
提示

请小心使用最终会具有相同调用者名称的过程!如果旧路由中的路径与新路由中的路径匹配,你将遇到问题。

¥Be careful of using procedures that will end up having the same caller name! You will run into issues if a path in your legacy router matches a path in your new router.

4. 在你的客户端中使用它

¥ Use it in your client

现在,你的客户端(作为 v10 调用者)将可以使用这两组过程。你现在需要 访问你的客户端代码以将调用者更新为 v10 语法

¥Both sets of procedures will now be available for your client as v10 callers. You will now need to visit your client code to update your callers to the v10 syntax.

ts
// Vanilla JS v10 client caller:
client.proxy.greeting.query();
// React v10 client caller:
trpc.proxy.greeting.useQuery();
ts
// Vanilla JS v10 client caller:
client.proxy.greeting.query();
// React v10 client caller:
trpc.proxy.greeting.useQuery();

互操作的局限性

¥Limitations of interop

订阅

¥Subscriptions

我们更改了订阅的 API,其中订阅需要返回 observable 实例。参见 订阅文档

¥We have changed the API of Subscriptions where subscriptions need to return an observable-instance. See subscriptions docs.

🚧 请随时为改进此部分做出贡献

¥🚧 Feel free to contribute to improve this section

自定义 HTTP 选项

¥Custom HTTP options

参见 HTTP 特定选项从 TRPCClient 移至链接

¥See HTTP-specific options moved from TRPCClient to links.

¥Custom Links

在 v10 中,Links 架构已被彻底改造。因此,为 v9 制作的自定义链接不适用于 v10 或在互操作时。如果你想了解有关如何为 v10 创建自定义链接的更多信息,请查看 链接文档

¥In v10, the Links architecture has been completely revamped. Therefore, custom links made for v9 will not work for v10 or while on interop. If you want more information about how to create a custom link for v10, checkout the Links documentation.

客户端包变更

¥Client Package Changes

v10 还对应用的客户端进行了更改。进行一些关键改变后,你将解锁一些关键的生活质量变化:

¥v10 also brings changes to the client side of your application. After making a few key changes, you'll unlock a few key quality of life changes:

  • 直接从客户端跳转到服务器定义

    ¥Jump to server definitions straight from your client

  • 直接从客户端重命名路由或程序

    ¥Rename routers or procedures straight from the client

@trpc/react-query

将 @trpc/react 重命名为 @trpc/react-query

¥Renaming of @trpc/react to @trpc/react-query

@trpc/react 软件包已重命名为 @trpc/react-query。这是为了反映它是 react-query 的薄封装器这一事实,并允许在没有 @trpc/react-query 包的情况下在 React 中使用 trpc 的情况,例如与即将推出的 React 服务器组件(RSC)或其他数据获取库一起使用 适配器。如果你使用的是 @trpc/react,则需要将其删除并安装 @trpc/react-query,并更新导入:

¥The @trpc/react package has been renamed to @trpc/react-query. This is to reflect the fact that it is a thin wrapper around react-query, as well as to allow for situations where trpc may be used in react without the @trpc/react-query package, such as with upcoming React Server Components (RSCs) or with other data fetching library adapters. If you're using @trpc/react, you'll need to remove it and install @trpc/react-query instead, as well as update your imports:

diff
- import { createReactQueryHooks } from '@trpc/react';
+ import { createReactQueryHooks } from '@trpc/react-query';
diff
- import { createReactQueryHooks } from '@trpc/react';
+ import { createReactQueryHooks } from '@trpc/react-query';

react-query 大版本升级

¥Major version upgrade of react-query

我们已将 peerDependenciesreact-query@^3 升级到 @tanstack/react-query@^4。因为我们的客户端钩子只是反应查询的一个薄封装,所以我们鼓励你 访问他们的迁移指南 了解有关新的 React 钩子实现的更多详细信息。

¥We've upgraded peerDependencies from react-query@^3 to @tanstack/react-query@^4. Because our client hooks are only a thin wrapper around react-query, we encourage you to visit their migration guide for more details about your new React hooks implementation.

钩子上的 tRPC 特定选项已移至 trpc

¥tRPC-specific options on hooks moved to trpc

为了避免与任何内置 react-query 属性发生冲突和混淆,我们已将所有 tRPC 选项移至名为 trpc 的属性。这个命名空间使特定于 tRPC 的选项变得清晰,并确保我们将来不会与 react-query 发生冲突。

¥To avoid collisions and confusion with any built-in react-query properties, we have moved all of the tRPC options to a property called trpc. This namespace brings clarity to options that are specific to tRPC and ensures that we won't collide with react-query in the future.

tsx
// Before
useQuery(['post.byId', '1'], {
context: {
batching: false,
},
});
// After:
useQuery(['post.byId', '1'], {
trpc: {
context: {
batching: false,
},
},
});
// or:
trpc.post.byId.useQuery('1', {
trpc: {
batching: false,
},
});
tsx
// Before
useQuery(['post.byId', '1'], {
context: {
batching: false,
},
});
// After:
useQuery(['post.byId', '1'], {
trpc: {
context: {
batching: false,
},
},
});
// or:
trpc.post.byId.useQuery('1', {
trpc: {
batching: false,
},
});

查询键值变化

¥Query key changes

注意

如果你在应用中仅使用 tRPC 提供的 API,则迁移不会有任何问题👍但是,如果你直接使用 tanstack 查询客户端来执行诸如使用 queryClient.setQueriesData 更新多个 tRPC 生成的查询的查询数据之类的操作,你可能需要注意 !

¥If you only use the tRPC provided APIs in your app you will have no problems in migrating 👍 However if you have been using the tanstack query client directly to do things like update query data for multiple tRPC generated queries using queryClient.setQueriesData you may need to take note!

为了让我们为一些更高级的功能(如 整个路由失效)腾出空间,我们需要改变在幕后使用 tanstack 查询键的方式。

¥To allow us to make room for some more advanced features like invalidation across whole routers, we needed to change how we use tanstack query keys under the hood.

我们已将使用的查询键从使用 . 连接字符串作为过程路径更改为元素子数组。当 queryinfinite 查询放入缓存时,我们还添加了它们之间的区别。我们还将此查询 type 和输入移动到具有命名属性的对象中。

¥We have changed the query keys we use from using a . joined string for the procedure path to a sub array of elements. We have also added a distinction between query's and infinite queries when they are placed in the cache. We have also moved both this query type and the input into an object with named properties.

给出下面的简单路由:

¥Given the simple router below:

tsx
export const appRouter = router({
user: router({
byId: publicProcedure
.input(z.object({ id: z.number() }))
.query((opts) => ({ user: { id: opts.input.id } })),
}),
});
tsx
export const appRouter = router({
user: router({
byId: publicProcedure
.input(z.object({ id: z.number() }))
.query((opts) => ({ user: { id: opts.input.id } })),
}),
});

用于 trpc.user.byId.useQuery({ id: 10 }) 的查询键将更改:

¥The query key used for trpc.user.byId.useQuery({ id: 10 }) would change:

  • 输入 V9:["user.byId", { id: 10 }]

    ¥Key in V9: ["user.byId", { id: 10 }]

  • 输入 v10:[["user", "byId"],{ input: { id:10 }, type: 'query' }]

    ¥Key in v10:[["user", "byId"],{ input: { id:10 }, type: 'query' }]

大多数开发者甚至不会注意到此更改,但对于直接使用 tanstack queryClient 操作 tRPC 生成的查询的一小部分人来说,他们将不得不更改他们过滤的密钥!

¥The majority of developers won't even notice this change, but for the small minority that are using the tanstack queryClient directly to manipulate tRPC generated queries, they will have to change the key they are filtering on!

@trpc/client

中止过程

¥Aborting procedures

在 v9 中,.cancel() 方法用于中止过程。

¥In v9, the .cancel() method was used to abort procedures.

对于 v10,我们已迁移到 AbortController Web API,以便更好地与 Web 标准保持一致。你将为查询提供 AbortSignal 并在其父级 AbortController 上调用 .abort(),而不是调用 .cancel()

¥For v10, we have moved to the AbortController Web API to align better with web standards. Instead of calling .cancel(), you'll give the query an AbortSignal and call .abort() on its parent AbortController.

tsx
const ac = new AbortController();
const helloQuery = client.greeting.query('KATT', { signal: ac.signal });
// Aborting
ac.abort();
tsx
const ac = new AbortController();
const helloQuery = client.greeting.query('KATT', { signal: ac.signal });
// Aborting
ac.abort();

¥HTTP-specific options moved from TRPCClient to links

以前,HTTP 选项(如标头)直接放置在 createTRPCClient()。然而,由于 tRPC 在技术上并不依赖于 HTTP 本身,因此我们已将它们从 TRPCClient 移至 httpLinkhttpBatchLink

¥Previously, HTTP options (like headers) were placed straight onto your createTRPCClient(). However, since tRPC is technically not tied to HTTP itself, we've moved these from the TRPCClient to httpLink and httpBatchLink.

ts
// Before:
import { createTRPCClient } from '@trpc/client';
const client = createTRPCClient({
url: '...',
fetch: myFetchPonyfill,
AbortController: myAbortControllerPonyfill,
headers() {
return {
'x-foo': 'bar',
};
},
});
// After:
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
const client = createTRPCProxyClient({
links: [
httpBatchLink({
url: '...',
fetch: myFetchPonyfill,
AbortController: myAbortControllerPonyfill,
headers() {
return {
'x-foo': 'bar',
};
},
})
]
});
ts
// Before:
import { createTRPCClient } from '@trpc/client';
const client = createTRPCClient({
url: '...',
fetch: myFetchPonyfill,
AbortController: myAbortControllerPonyfill,
headers() {
return {
'x-foo': 'bar',
};
},
});
// After:
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
const client = createTRPCProxyClient({
links: [
httpBatchLink({
url: '...',
fetch: myFetchPonyfill,
AbortController: myAbortControllerPonyfill,
headers() {
return {
'x-foo': 'bar',
};
},
})
]
});

此更改也反映在 @trpc/server 包中,其中 http 相关导出以前是从主入口点导出的,但现在已移至其自己的 @trpc/server/http 入口点。

¥This change is also reflected in the @trpc/server package, where http related exports were previously exported from the main entrypoint but have now been moved to their own @trpc/server/http entrypoint.

附加功能

¥Extras

删除拆卸选项

¥Removal of the teardown option

拆卸选项已被删除并且不再可用。

¥The teardown option has been removed and is no longer available.

createContext 返回类型

¥createContext return type

createContext 函数不能再返回 nullundefined。如果你没有使用自定义上下文,则必须返回一个空对象:

¥The createContext function can no longer return either null or undefined. If you weren't using a custom context, you'll have to return an empty object:

diff
- createContext: () => null,
+ createContext: () => ({}),
diff
- createContext: () => null,
+ createContext: () => ({}),

queryClient 不再通过 tRPC 上下文公开

¥queryClient is no longer exposed through tRPC context

tRPC 不再通过 trpc.useContext() 公开 queryClient 实例。如果你需要使用 queryClient 中的某些方法,请检查 trpc.useContext() 是否封装了它们 此处。如果 tRPC 尚未封装相应的方法,你可以从 @tanstack/react-query 导入 queryClient 并以这种方式使用它:

¥tRPC is no longer exposing the queryClient instance through trpc.useContext(). If you need to use some methods from queryClient, check if trpc.useContext() wraps them here. If tRPC doesn't wrap the respective method yet, you can import the queryClient from @tanstack/react-query and use it that way:

tsx
import { useQueryClient } from '@tanstack/react-query';
const MyComponent = () => {
const queryClient = useQueryClient();
// ...
};
tsx
import { useQueryClient } from '@tanstack/react-query';
const MyComponent = () => {
const queryClient = useQueryClient();
// ...
};

迁移自定义错误格式化程序

¥Migrate custom error formatters

你需要将 formatError() 的内容移至根 t 路由中。有关更多信息,请参阅 文档格式错误

¥You will need to move the contents of your formatError() into your root t router. See the Error Formatting docs for more.