跳至主内容
版本:11.x

服务器操作

非官方测试版翻译

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

服务器操作(Server Actions)允许你在服务器端定义函数,并直接从客户端组件调用,框架会抽象处理网络层。

通过 tRPC 过程(procedures)定义服务器操作,你可以获得 tRPC 的所有内置功能:输入验证、通过中间件(middleware)实现的认证授权、输出验证、数据转换器等。

信息

服务器操作集成使用 experimental_ 前缀,目前仍在积极开发中。API 可能会在未来的版本中变更。

设置服务器操作过程

1. 使用 experimental_caller 定义基础过程

在过程构建器上使用 experimental_callerexperimental_nextAppDirCaller 来创建可直接作为普通函数调用的过程(即服务器操作)。pathExtractor 选项允许你通过元数据识别过程,这对于日志记录和可观察性非常有用,因为服务器操作没有类似 user.byId 的路由路径。

server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
);
server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
);

2. 通过中间件添加上下文

由于服务器操作不经过 HTTP 适配器,因此没有 createContext 来注入上下文。取而代之的是,使用中间件提供上下文(例如会话数据):

server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
import { currentUser } from '../auth';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure
.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
)
.use(async (opts) => {
const user = await currentUser();
return opts.next({ ctx: { user } });
});
server/trpc.ts
ts
import { initTRPC, TRPCError } from '@trpc/server';
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';
import { currentUser } from '../auth';
 
interface Meta {
span: string;
}
 
export const t = initTRPC.meta<Meta>().create();
 
export const serverActionProcedure = t.procedure
.experimental_caller(
experimental_nextAppDirCaller({
pathExtractor: ({ meta }) => (meta as Meta)?.span ?? '',
}),
)
.use(async (opts) => {
const user = await currentUser();
return opts.next({ ctx: { user } });
});

3. 创建受保护的操作过程

3. 创建受保护的操作过程

server/trpc.ts
ts
export const protectedAction = serverActionProcedure.use((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
 
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // ensures type is non-nullable
},
});
});
server/trpc.ts
ts
export const protectedAction = serverActionProcedure.use((opts) => {
if (!opts.ctx.user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
 
return opts.next({
ctx: {
...opts.ctx,
user: opts.ctx.user, // ensures type is non-nullable
},
});
});

添加授权中间件,为需要认证的操作创建可复用的基础:

创建一个包含 "use server" 指令的文件,并使用过程构建器定义您的服务器操作:

app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// opts.ctx.user is typed as non-nullable
// opts.input is typed as { title: string }
// Create the post...
});
app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// opts.ctx.user is typed as non-nullable
// opts.input is typed as { title: string }
// Create the post...
});

由于 experimental_caller,该过程现在是一个普通的异步函数,可以用作服务器操作。

从客户端组件调用

导入服务器操作并在客户端组件中使用。服务器操作既支持渐进增强的 action 属性,也支持通过 onSubmit 进行编程式调用:

app/post-form.tsx
tsx
'use client';
 
import { createPost } from '../_actions';
 
export function PostForm() {
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const title = new FormData(e.currentTarget).get('title') as string;
await createPost({ title });
}}
>
<input type="text" name="title" />
<button type="submit">Create Post</button>
</form>
);
}
app/post-form.tsx
tsx
'use client';
 
import { createPost } from '../_actions';
 
export function PostForm() {
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const title = new FormData(e.currentTarget).get('title') as string;
await createPost({ title });
}}
>
<input type="text" name="title" />
<button type="submit">Create Post</button>
</form>
);
}

使用元数据增强可观察性

使用 .meta() 方法为操作添加日志记录或追踪标签。元数据中的 span 属性会传递给 pathExtractor,因此可被可观察性工具使用:

app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.meta({ span: 'create-post' })
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// ...
});
app/_actions.ts
ts
'use server';
 
import { z } from 'zod';
import { protectedAction } from '../server/trpc';
 
export const createPost = protectedAction
.meta({ span: 'create-post' })
.input(
z.object({
title: z.string(),
}),
)
.mutation(async (opts) => {
// ...
});

何时使用服务器操作 vs 变更(mutations)

服务器操作并非用于替代所有 tRPC 变更。请考虑以下权衡:

  • 使用服务器操作:当需要渐进增强(表单在不支持 JavaScript 时仍能工作),或操作不需要更新客户端 React Query 缓存时

  • 使用 useMutation:当需要更新客户端缓存、展示乐观更新(optimistic updates)或管理 UI 中的复杂加载/错误状态时

你可以逐步采用服务器操作,与现有 tRPC API 并存——无需重写整个 API。