前言
React Server Actions 是 React 19 引入的一项革命性特性,允许直接在服务器端执行函数,无缝处理数据突变(mutations)。它简化了传统的前后端交互模式,尤其适合表单提交、数据更新等场景。本文将带你从零开始,掌握 Server Actions 的核心用法、最佳实践以及常见陷阱,让你在实际项目中游刃有余。
环境准备
确保你的项目使用 React 19 及以上版本,并搭配 Next.js 14+(App Router)或支持 Server Actions 的框架。本文示例基于 Next.js 14。
npx create-next-app@latest my-app --typescript
cd my-app
基础入门:第一个 Server Action
1. 定义 Action
在 Next.js 中,Server Actions 可以定义在文件顶部(使用 "use server" 指令)或单独的文件中。最简单的形式:
// app/actions.ts
"use server";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
// 模拟数据库操作
console.log("Creating user:", { name, email });
return { success: true };
}
2. 在客户端组件中使用
// app/page.tsx
"use client";
import { createUser } from "./actions";
export default function Home() {
return (
<form action={createUser}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Submit</button>
</form>
);
}
> 💡 注意:action 属性直接接收 Server Action 函数,无需手动调用 fetch 或 axios。表单提交会自动发送请求并处理响应。
进阶用法:数据验证与错误处理
1. 使用 Zod 进行验证
"use server";
import { z } from "zod";
const schema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email"),
});
export async function createUser(formData: FormData) {
const validated = schema.safeParse({
name: formData.get("name"),
email: formData.get("email"),
});
if (!validated.success) {
return { errors: validated.error.flatten().fieldErrors };
}
// 处理有效数据
console.log("Validated data:", validated.data);
return { success: true };
}
2. 客户端展示错误
"use client";
import { createUser } from "./actions";
import { useActionState } from "react";
export default function Home() {
const [state, formAction] = useActionState(createUser, { errors: {} });
return (
<form action={formAction}>
<input name="name" placeholder="Name" />
{state.errors?.name && <p style={{ color: "red" }}>{state.errors.name}</p>}
<input name="email" type="email" placeholder="Email" />
{state.errors?.email && <p style={{ color: "red" }}>{state.errors.email}</p>}
<button type="submit">Submit</button>
</form>
);
}
> 💡 提示:useActionState(React 19 新增)可以管理 Action 的状态,包括错误和加载状态。
客户端组件与 Server Actions 的交互
1. 调用 Server Action 并刷新数据
"use client";
import { revalidatePath } from "next/cache";
import { deleteUser } from "./actions";
export function DeleteButton({ userId }: { userId: string }) {
const handleDelete = async () => {
await deleteUser(userId);
// 注意:不能直接在客户端组件中调用 revalidatePath,需要从 Action 内部触发
};
return <button onClick={handleDelete}>Delete</button>;
}
正确做法:在 Server Action 中调用 revalidatePath。
"use server";
import { revalidatePath } from "next/cache";
export async function deleteUser(userId: string) {
// 删除用户逻辑
revalidatePath("/users"); // 重新获取用户列表
return { success: true };
}
2. 传递非表单数据
如果 Action 需要接收非表单数据(如 JSON 对象),可以通过 bind 或直接调用:
"use client";
import { updateUser } from "./actions";
export function UpdateButton({ user }: { user: { id: string; name: string } }) {
const handleUpdate = async () => {
await updateUser(user);
};
return <button onClick={handleUpdate}>Update</button>;
}
"use server";
export async function updateUser(user: { id: string; name: string }) {
// 更新逻辑
}
常见陷阱与最佳实践
1. 避免在客户端组件中直接修改状态
Server Actions 是异步的,不要在 onClick 中直接调用并期望立即更新状态。使用 useActionState 或 startTransition 来管理。
2. 安全性:永远不要信任客户端数据
始终在 Server Action 中进行验证和授权检查。
"use server";
export async function deleteUser(userId: string) {
const session = await getSession();
if (!session || session.user.id !== userId) {
throw new Error("Unauthorized");
}
// 删除逻辑
}
3. 使用 useActionState 处理加载状态
const [state, formAction, isPending] = useActionState(createUser, { errors: {} });
return (
<form action={formAction}>
<button disabled={isPending}>{isPending ? "Submitting..." : "Submit"}</button>
</form>
);
4. 错误边界
对于未捕获的错误,可以使用 React 错误边界或 Next.js 的 error.js 文件。
总结
React Server Actions 极大简化了数据突变流程,让前端开发者无需手动管理 API 路由。通过本文的学习,你应该掌握了:
- 定义和使用 Server Actions
- 结合 Zod 进行数据验证
- 在客户端组件中处理状态和错误
- 调用 Server Actions 并触发缓存刷新
- 安全性和性能最佳实践
下一步,可以探索 Server Actions 与 Streaming、Suspense 的结合,以及如何在大型项目中组织 Action 代码。