React Server Actions 入门与进阶:从表单提交到乐观更新实战

By | 2026年9月11日

为什么需要 Server Actions?

在传统的 React 应用中,前后端交互通常需要手动创建 API 路由、编写 fetch 请求、处理加载和错误状态。这不仅繁琐,还容易导致代码重复和类型不安全。React Server Actions(RSC Actions)允许你在组件中直接定义异步函数,在服务器端运行,并通过网络调用,从而简化数据变更逻辑。

Server Actions 是 React 18/19 中与 Server Components 配合的重要特性,它让你能像调用本地函数一样调用服务器函数,同时保持渐进增强和类型安全。本文将从零开始,带你掌握 Server Actions 的核心用法,并深入实战场景。

环境准备

首先,确保你使用支持 Server Actions 的框架,例如 Next.js 14+(App Router)或 Remix。本文以 Next.js 14 为例。


npx create-next-app@latest my-app --typescript --app
cd my-app

在 next.config.js 中启用实验性功能(若使用 Next.js 14,默认已启用):


/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
};

module.exports = nextConfig;

第一步:创建第一个 Server Action

Server Action 本质上是一个异步函数,使用 'use server' 指令标记。它可以在服务端组件中定义,也可以在单独的文件中定义。

在 app/actions.ts 中:


'use server';

export async function createTodo(formData: FormData) {
  const title = formData.get('title') as string;
  if (!title || title.trim().length === 0) {
    throw new Error('Title is required');
  }
  // 模拟数据库插入
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log('Todo created:', title);
  return { success: true };
}

在服务端组件中直接使用:


// app/page.tsx
import { createTodo } from './actions';

export default function Home() {
  return (
    <form action={createTodo}>
      <input type="text" name="title" placeholder="Enter todo" />
      <button type="submit">Add</button>
    </form>
  );
}

当表单提交时,React 会自动调用 createTodo,并将表单数据作为 FormData 传入。无需手动 fetch,也无需定义 API 路由。

💡 注意:Server Action 必须是一个异步函数,且只能在服务端组件或客户端组件中通过 action 属性或 startTransition 调用。

第二步:处理错误与加载状态

在客户端组件中,你可以使用 useFormStatus 获取表单提交状态,或使用 useActionState(React 19)管理返回值和错误。


'use client';
import { useFormStatus } from 'react-dom';
import { createTodo } from './actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Adding...' : 'Add'}
    </button>
  );
}

export function TodoForm() {
  return (
    <form action={createTodo}>
      <input type="text" name="title" required />
      <SubmitButton />
    </form>
  );
}

对于错误处理,可以结合 useActionState(以前叫 useFormState):


'use client';
import { useActionState } from 'react';
import { createTodo } from './actions';

const initialState = { error: null };

export function TodoForm() {
  const [state, formAction] = useActionState(async (prevState, formData) => {
    try {
      await createTodo(formData);
      return { error: null };
    } catch (e) {
      return { error: e.message };
    }
  }, initialState);

  return (
    <form action={formAction}>
      <input type="text" name="title" required />
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      <button type="submit">Add</button>
    </form>
  );
}

第三步:进阶——乐观更新与重新验证

Server Actions 常与 useOptimistic 和 revalidatePath 搭配,实现即时 UI 反馈和数据刷新。

假设我们有一个待办列表,从数据库获取数据。在 Server Action 中插入后,调用 revalidatePath('/') 让页面重新获取最新数据。


'use server';
import { revalidatePath } from 'next/cache';

export async function addTodo(formData: FormData) {
  const title = formData.get('title') as string;
  // 插入数据库...
  revalidatePath('/');
}

在客户端组件中,使用 useOptimistic 实现乐观更新:


'use client';
import { useOptimistic } from 'react';
import { addTodo } from './actions';

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

  async function formAction(formData: FormData) {
    const title = formData.get('title') as string;
    addOptimisticTodo(title);
    await addTodo(formData);
  }

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

这样,用户提交后立即看到新待办,而无需等待服务器响应。

第四步:安全最佳实践

Server Actions 直接暴露在网络上,必须进行安全防护:

  1. 始终验证输入:使用 Zod 等库进行模式验证。
  2. 认证与授权:在 Action 内部检查用户会话,避免未授权访问。
  3. 限制暴露的 Action:避免在客户端组件中导入未经验证的 Action。

示例:使用 Zod 验证并检查认证。


'use server';
import { z } from 'zod';
import { auth } from '@/auth'; // 假设的认证工具

const schema = z.object({
  title: z.string().min(1).max(100),
});

export async function createTodo(formData: FormData) {
  const session = await auth();
  if (!session) throw new Error('Unauthorized');

  const parsed = schema.safeParse({ title: formData.get('title') });
  if (!parsed.success) throw new Error('Invalid input');

  // 安全地使用 parsed.data.title
}

常见坑与性能优化

  • 不要在客户端组件中直接定义 Server Action:必须使用 'use server' 指令,且函数必须是异步的。
  • 避免在 Action 中执行长时间任务:Server Action 默认有超时限制,长时间任务应使用后台作业。
  • 合理使用 revalidatePath 和 revalidateTag:过度重新验证会导致性能下降,建议按需使用。
  • 错误处理:抛出错误会触发最近的错误边界,建议返回结构化结果而非直接抛出。

总结

React Server Actions 极大地简化了全栈 React 应用的数据变更逻辑,通过本文的入门与进阶示例,你应该已经掌握了从表单提交到乐观更新的完整流程。记住:始终验证输入、检查认证、合理使用重新验证,才能构建出既高效又安全的应用。

下一步,你可以探索 Server Actions 与 Server Components 的深度集成、使用 useActionState 管理复杂表单状态,以及结合数据库 ORM 实现完整 CRUD。