React Server Actions 入门与进阶:从表单处理到数据突变的最佳实践

By | 2026年7月5日

为什么需要 Server Actions?

在传统的 React 应用中,处理表单提交通常需要以下步骤:

  1. 在前端定义表单组件,管理表单状态。
  2. 编写 API 路由(如 Next.js 的 Route Handlers 或 Express 端点)。
  3. 在表单提交时,通过 fetch 或 axios 发送请求。
  4. 在服务端处理数据,并返回响应。
  5. 在前端处理响应,更新 UI 或显示错误。

这个过程涉及大量样板代码,而且前后端逻辑分离使得维护变得困难。React Server Actions 彻底改变了这一现状:它允许你直接在服务端组件中定义异步函数,然后在客户端组件中像调用普通函数一样调用它们。数据突变变得像调用一个本地函数一样简单,同时保持了服务端的安全性和性能优势。

基础用法:第一个 Server Action

首先,确保你的项目使用 React 19 或 Next.js 14+(App Router)。Server Actions 可以用在服务端组件中,也可以用在客户端组件中(通过 "use server" 指令)。

1. 在服务端组件中定义 Action


// app/actions.ts (Server Component)
"use server";

export async function createUser(formData: FormData) {
  const name = formData.get("name") as string;
  const email = formData.get("email") as string;

  // 模拟数据库操作
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log(`User created: ${name} (${email})`);
  return { success: true, message: "User created!" };
}

> 💡 注意:"use server" 指令必须位于文件的顶部,并且只能导出异步函数。

2. 在客户端组件中使用 Action


// app/page.tsx (Client Component)
"use client";

import { createUser } from "./actions";

export default function Home() {
  return (
    <form action={createUser}>
      <input type="text" name="name" placeholder="Name" required />
      <input type="email" name="email" placeholder="Email" required />
      <button type="submit">Create User</button>
    </form>
  );
}

当表单提交时,React 会自动将 FormData 传递给 createUser 函数,并在服务端执行。整个过程无需手动发送 HTTP 请求,也无需管理加载状态。

3. 处理加载状态与表单重置

虽然 action 属性会自动处理提交,但有时我们需要在客户端显示加载状态。可以使用 useFormStatus 钩子:


// app/SubmitButton.tsx
"use client";

import { useFormStatus } from "react-dom";

export function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Submitting..." : "Create User"}
    </button>
  );
}

然后在表单中使用:


<form action={createUser}>
  <input type="text" name="name" required />
  <input type="email" name="email" required />
  <SubmitButton />
</form>

useFormStatus 必须在表单的子组件中使用,它会自动读取最近的

的提交状态。

进阶用法:表单验证与错误处理

1. 使用 Zod 进行服务端验证


// app/actions.ts
"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 address"),
});

export async function createUser(formData: FormData) {
  const validatedFields = schema.safeParse({
    name: formData.get("name"),
    email: formData.get("email"),
  });

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      message: "Validation failed",
    };
  }

  const { name, email } = validatedFields.data;
  // 数据库操作...
  return { success: true, message: "User created!" };
}

2. 在客户端显示错误

使用 useActionState 钩子(React 19 中替代 useFormState):


// app/UserForm.tsx
"use client";

import { useActionState } from "react";
import { createUser } from "./actions";

export function UserForm() {
  const [state, formAction] = useActionState(createUser, {
    message: "",
    errors: {},
  });

  return (
    <form action={formAction}>
      <div>
        <input type="text" name="name" placeholder="Name" />
        {state.errors?.name && <p style={{ color: "red" }}>{state.errors.name[0]}</p>}
      </div>
      <div>
        <input type="email" name="email" placeholder="Email" />
        {state.errors?.email && <p style={{ color: "red" }}>{state.errors.email[0]}</p>}
      </div>
      <button type="submit">Create User</button>
      {state.message && <p>{state.message}</p>}
    </form>
  );
}

useActionState 接收 action 函数和初始状态,返回当前状态和新的 formAction。当 action 执行后,返回的状态会自动更新。

进阶用法:乐观更新(Optimistic Updates)

乐观更新是提升用户体验的重要模式:在请求完成前就更新 UI,如果失败则回滚。


// app/TodoList.tsx
"use client";

import { useOptimistic, useRef } from "react";
import { addTodo } from "./actions";

export function TodoList({ todos }: { todos: string[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo: string) => [...state, newTodo]
  );
  const formRef = useRef<HTMLFormElement>(null);

  async function formAction(formData: FormData) {
    const todo = formData.get("todo") as string;
    addOptimisticTodo(todo);
    formRef.current?.reset();
    await addTodo(formData);
  }

  return (
    <>
      <form action={formAction} ref={formRef}>
        <input type="text" name="todo" required />
        <button type="submit">Add</button>
      </form>
      <ul>
        {optimisticTodos.map((todo, i) => (
          <li key={i}>{todo}</li>
        ))}
      </ul>
    </>
  );
}

useOptimistic 接受当前状态和一个更新函数,返回一个乐观状态和一个用于触发乐观更新的函数。在 formAction 中,先调用 addOptimisticTodo 立即添加待办事项,然后执行实际的 action。如果 action 失败,React 会自动回滚状态。

最佳实践与常见陷阱

1. 始终在服务端进行验证

不要依赖客户端验证,因为恶意用户可以绕过。始终在 Server Action 中使用 Zod 或类似库进行验证。

2. 使用事务和错误边界

对于涉及多个数据库操作的 action,使用事务确保数据一致性。同时,在客户端使用错误边界捕获未预期的错误。

3. 避免在 Action 中直接返回敏感数据

Server Actions 的返回值会序列化并发送给客户端,因此不要返回密码、令牌等敏感信息。

4. 注意 Action 的重新执行

在 React 严格模式下,action 可能会被调用两次(开发环境)。确保你的 action 是幂等的,或者使用唯一标识符防止重复提交。

5. 组合多个 Action

如果一个页面需要多个不同的 action,可以将它们放在单独的文件中,或者使用对象导出:


// app/actions.ts
export const userActions = {
  create: async (formData: FormData) => { ... },
  update: async (formData: FormData) => { ... },
};

6. 与外部 API 交互

Server Actions 可以调用外部 API,但注意不要暴露 API 密钥。将密钥放在环境变量中,并在服务端使用。

总结

React Server Actions 简化了数据突变的流程,减少了样板代码,并提升了安全性。通过本文的学习,你应该能够:

  • 创建和使用基础的 Server Action
  • 处理表单验证和错误
  • 实现乐观更新
  • 应用最佳实践避免常见陷阱

下一步,你可以探索 Server Actions 与流式传输(Streaming)的结合,或者学习如何在 Next.js 中使用 Server Actions 配合缓存和重新验证(Revalidation)。

参考资料