Server Actions 是 React 19 中正式引入的一种新模式,它允许你在服务端定义异步函数,然后在客户端组件中像调用本地函数一样直接调用它们。React 会在幕后自动处理网络请求、参数序列化、响应处理以及界面更新,让你不用手动搭建 API 路由和编写 fetch 逻辑。
简单地说:Server Actions 让你把“客户端触发事件”和“服务端执行逻辑”无缝连接起来,就像同一个代码上下文里完成的一样。
为什么需要 Server Actions
在传统的前后端分离架构中,客户端想要修改服务端数据,通常需要:
- 编写一个服务端 API 路由(如
/api/update-profile)。 - 在客户端通过
fetch或 Axios 发送请求,手动序列化参数。 - 处理响应、错误以及加载状态。
- 手动刷新页面数据或乐观更新 UI。
这个过程会引入大量样板代码,而且客户端和服务端之间的数据格式、校验逻辑、错误处理容易散落各处。Server Actions 将这些步骤抽象掉:你只需要写一个普通的异步函数(放在服务端执行),然后在客户端表单的 action 属性或按钮的 onClick 里引用它,剩下的都由 React 协同框架(如 Next.js)完成。
核心机制与底层原理
Server Actions 基于 RSC(React Server Components) 的通信能力。一个典型的 Server Action 具有以下特征:
- 使用
'use server'指令标记该函数只能在服务端运行。 - 函数参数会被自动序列化(按照 React 的服务端-客户端协议)并发送到服务端。
- 服务端执行完成后,可以选择返回一个新的 UI React Node(用于乐观更新或错误回显),也可以只返回一个可序列化的结果(如 JSON 对象、错误信息)。
- 在并发渲染模式下,Server Action 可以自动结合
useTransition、useOptimistic等 Hooks 提供加载状态和乐观反馈。
整个过程对开发者透明,你不需要关心 fetch、请求头、响应状态码——这极大降低了数据变更类交互的开发成本。
实战示例:表单提交
下面是一个使用 Next.js App Router 实现“修改用户名”的完整例子,展示了 Server Actions 的全部关键步骤。
1. 定义 Server Action
在服务端组件文件中(例如 app/profile/page.tsx)或一个独立的 actions.ts 里,编写一个异步函数,加上 'use server':
// actions.ts
'use server';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, '姓名不能为空'),
});
export async function updateName(formData: FormData) {
const parsed = schema.parse({
name: formData.get('name'),
});
// 假设从 cookie 或 session 中拿到当前用户 ID
const userId = await getCurrentUserId();
await db.user.update({
where: { id: userId },
data: { name: parsed.name },
});
// 让缓存失效,页面重新获取最新数据
revalidatePath('/profile');
// 也可以直接返回一条消息用于客户端展示
return { success: true };
}
2. 在客户端组件中调用
在需要提交的表单里,可以直接将 Server Action 作为 <form> 的 action 属性值,无论是客户端组件还是服务端组件都可以:
// ProfileForm.tsx (客户端组件)
'use client';
import { useFormStatus } from 'react-dom';
import { updateName } from './actions';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '保存中...' : '保存'}
</button>
);
}
export default function ProfileForm() {
return (
<form action={updateName}>
<input name="name" placeholder="新姓名" required />
<SubmitButton />
</form>
);
}
这个表单在提交时:
- 浏览器会将
<input>的当前值收集为FormData,并自动传递给updateName。 - 在
updateName执行期间,useFormStatus会返回pending: true,按钮自动禁用并显示“保存中...”。 - 执行完毕后,由于
revalidatePath调用了,页面对应的数据会自动刷新,显示新用户名。
3. 处理错误与返回结果
Server Action 可以通过抛出异常来返回错误,也可以用更灵活的方式返回普通对象。React 增强的 useActionState 和 useFormState(在 19 中标准化)可以帮助你在 UI 上展示服务端返回的消息:
'use client';
import { useActionState } from 'react';
import { updateName } from './actions';
export default function ProfileForm() {
const [state, formAction] = useActionState(updateName, { success: false });
return (
<form action={formAction}>
<input name="name" />
{state?.error && <p className="text-red-500">{state.error}</p>}
{state?.success && <p className="text-green-500">更新成功</p>}
<SubmitButton />
</form>
);
}
并在 actions.ts 中修改 updateName 的返回结构,为两种状态提供消息:
export async function updateName(prevState: any, formData: FormData) {
try {
// ... 更新逻辑
return { success: true };
} catch (error) {
return { error: '更新失败,请重试' };
}
}
非表单场景的调用
Server Action 不仅局限于表单,你可以在任何客户端交互(如按钮点击、拖拽完成)中调用它。React 提供了 startTransition 包裹 Server Action 调用,从而让 UI 保持响应,并显示 pending 状态:
import { startTransition } from 'react';
import { updateProfile } from './actions';
function Button() {
const handleClick = () => {
startTransition(async () => {
const result = await updateProfile({ bio: '新个人简介' });
if (result.success) {
// 展示成功 toast
}
});
};
return <button onClick={handleClick}>更新简介</button>;
}
注意:这种直接调用方式目前在一些框架中尚处于实验阶段,具体 API 需以框架文档为准,但核心思想相同。
Server Actions 与传统 API 路由的对比
| 特性 | Server Actions | 传统 API 路由 |
|------|----------------|----------------|
| 样板代码 | 极少,函数定义即接口 | 需要编写路由、请求处理、序列化 |
| 类型安全 | 天然端到端类型提示 | 需要手动维护请求/响应类型 |
| 加载状态 | 结合 useFormStatus / useTransition 自动获得 | 需要手动管理 loading、error 状态 |
| 错误处理 | 可以通过返回对象或抛出异常 | 需要自定义错误响应并解析 |
| 用途 | 数据变更(增删改)为主 | 适用于查询、外部回调、Webhook 等 |
| 面向的组件 | 主要配合服务端组件和表单 | 任何客户端操作 |
Server Actions 并不是要完全消灭 API 路由,而是把与组件直接相关的数据修改操作下沉到组件侧,减少开发者的上下文切换,让代码更内聚。
安全性与使用约束
Server Actions 背后是服务端运行的代码,因此必须注意安全:
- 鉴权:与 API 路由一样,必须在函数体内验证用户身份,不能假设调用者就是合法用户。
- 输入校验:永远不要信任客户端传过来的参数,应使用 Zod 等库进行严格校验。
- 闭包限制:
'use server'函数不能从外部作用域获取客户端相关的闭包变量(如useState里的值),只能接收序列化参数。 - 体积与性能:Server Action 的执行是在服务端,每次调用都会产生一次网络往返,不适用于高频操作(如实时输入建议)。
总结
Server Actions 是 React 对通用数据变更模式的一次极具创造力的封装,它把原本需要两端各写一段代码的割裂体验,缩减到了一个函数定义、一行引用的程度。有了 Server Actions,表单提交、用户操作等工作变得异常简练,同时保持了类型安全与服务端逻辑的私密性。掌握 Server Actions 是现代 React 全栈开发的重要一环,配合 RSC、useOptimistic 等特性,可以构建出用户体验极佳且代码量极少的应用程序。