React Server Actions 入门与进阶:从表单提交到全栈数据流

By | 2026年9月14日

为什么需要 Server Actions?

在传统的 React 全栈开发中,我们通常需要:

  1. 创建一个 API 路由(如 /api/submit)
  2. 在前端用 fetch 调用该接口
  3. 手动处理 loading、错误和重新验证

这种模式代码分散、样板代码多,且容易遗漏加载状态。React Server Actions(以下简称 Server Actions)允许你在组件中直接定义异步服务端函数,并通过表单或事件调用它们,从而简化数据变更流程。

Server Actions 是 React 19 的稳定特性,Next.js 13.4+ 已内置支持。本文将基于 Next.js 14(App Router)进行演示,但核心概念同样适用于其他支持 React Server Components 的框架。

环境准备

确保你已安装 Node.js 18.17 或更高版本。创建一个新的 Next.js 项目:


npx create-next-app@latest server-actions-demo --typescript --app --tailwind --eslint
cd server-actions-demo

安装必要依赖(本文会用到 zod 做数据验证):


npm install zod

启动开发服务器:


npm run dev

第一步:定义一个最简单的 Server Action

在 app/actions.ts 中创建一个 Server Action:


// app/actions.ts
'use server';

export async function greet(name: string) {
  // 模拟异步操作
  await new Promise((resolve) => setTimeout(resolve, 500));
  return `你好,${name}!`;
}

'use server' 指令告诉 React 这个模块中的所有导出函数都是 Server Actions,它们只能在服务端执行。

在客户端组件中调用它:


// app/components/GreetForm.tsx
'use client';

import { useState } from 'react';
import { greet } from '../actions';

export default function GreetForm() {
  const [message, setMessage] = useState('');

  async function handleSubmit(formData: FormData) {
    const name = formData.get('name') as string;
    const result = await greet(name);
    setMessage(result);
  }

  return (
    <form action={handleSubmit}>
      <input name="name" placeholder="输入你的名字" required />
      <button type="submit">打招呼</button>
      {message && <p>{message}</p>}
    </form>
  );
}

注意:

是 React 19 的新特性,它接受一个函数(可以是 Server Action 或客户端函数)。当表单提交时,React 会自动调用该函数并传入 FormData。

第二步:在服务端组件中直接使用 Server Action

Server Actions 也可以直接在服务端组件中定义和使用,无需 'use client':


// app/server-form/page.tsx
import { redirect } from 'next/navigation';

export default function ServerFormPage() {
  async function submitForm(formData: FormData) {
    'use server';
    const email = formData.get('email') as string;
    // 模拟保存到数据库
    console.log('保存邮箱:', email);
    redirect('/success');
  }

  return (
    <form action={submitForm}>
      <input name="email" type="email" placeholder="你的邮箱" required />
      <button type="submit">订阅</button>
    </form>
  );
}

这里 submitForm 是内联的 Server Action,它只能在该组件内使用。redirect 来自 next/navigation,用于服务端重定向。

第三步:结合 useFormState 处理表单状态

React 19 提供了 useFormState(在 Next.js 中从 react-dom 导入)来管理表单提交后的状态和错误信息。

首先定义 action:


// app/actions.ts
'use server';

export async function subscribe(prevState: any, formData: FormData) {
  const email = formData.get('email') as string;
  
  if (!email || !email.includes('@')) {
    return { error: '请输入有效的邮箱地址' };
  }
  
  // 模拟保存
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return { success: `订阅成功:${email}` };
}

在客户端组件中使用:


// app/components/SubscribeForm.tsx
'use client';

import { useFormState, useFormStatus } from 'react-dom';
import { subscribe } from '../actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '订阅'}
    </button>
  );
}

export default function SubscribeForm() {
  const [state, formAction] = useFormState(subscribe, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" placeholder="你的邮箱" required />
      <SubmitButton />
      {state?.error && <p style={{ color: 'red' }}>{state.error}</p>}
      {state?.success && <p style={{ color: 'green' }}>{state.success}</p>}
    </form>
  );
}

useFormStatus 必须用在

的子组件中,它提供 pending 状态,避免手动管理 loading。

第四步:进阶——乐观更新与错误回滚

对于点赞、收藏等交互,我们希望界面立即响应,而不是等待服务端返回。React 19 的 useOptimistic 可以轻松实现乐观更新。


// app/components/LikeButton.tsx
'use client';

import { useOptimistic } from 'react';
import { likePost } from '../actions';

export default function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    initialLikes,
    (state, newLike: number) => state + newLike
  );

  async function handleLike() {
    addOptimisticLike(1);
    try {
      await likePost(postId);
    } catch (error) {
      // 错误时 React 会自动回滚到 initialLikes
      alert('点赞失败,请重试');
    }
  }

  return (
    <button onClick={handleLike}>
      👍 {optimisticLikes}
    </button>
  );
}

对应的 Server Action:


// app/actions.ts
'use server';

export async function likePost(postId: string) {
  // 模拟数据库更新
  await new Promise((resolve) => setTimeout(resolve, 800));
  // 随机失败以演示回滚
  if (Math.random() < 0.3) {
    throw new Error('点赞失败');
  }
  console.log('点赞成功:', postId);
}

注意:useOptimistic 的第二个参数是更新函数,它接收当前状态和乐观值,返回新状态。当 action 完成后,React 会使用真实数据重新渲染。

第五步:安全与验证最佳实践

Server Actions 是公开的 HTTP 端点,必须进行输入验证和权限检查。推荐使用 zod:


// app/actions.ts
'use server';

import { z } from 'zod';

const schema = z.object({
  title: z.string().min(1, '标题不能为空').max(100),
  content: z.string().min(10, '内容至少10个字符'),
});

export async function createPost(prevState: any, formData: FormData) {
  const raw = {
    title: formData.get('title'),
    content: formData.get('content'),
  };

  const result = schema.safeParse(raw);
  if (!result.success) {
    return { errors: result.error.flatten().fieldErrors };
  }

  // 检查用户身份(示例)
  // const session = await getSession();
  // if (!session) return { error: '请先登录' };

  // 保存到数据库
  await savePost(result.data);
  return { success: true };
}

💡 重要提醒:永远不要信任客户端传入的数据,即使表单有 required 属性。服务端验证是必须的。

常见坑与解决方案

1. 在客户端组件中导入 Server Action 报错

确保文件顶部有 'use server',且只导出异步函数。如果混合导出同步函数,会报错。

2. 无法在 Server Action 中使用 React Hooks

Server Actions 在服务端执行,不能使用 useState、useEffect 等。如果需要状态,请通过参数传递或使用 cookies()、headers() 等服务端 API。

3. 重新验证数据

在 Server Action 中修改数据后,需要调用 revalidatePath 或 revalidateTag 来更新缓存:


import { revalidatePath } from 'next/cache';

export async function addTodo(formData: FormData) {
  'use server';
  const todo = formData.get('todo') as string;
  await db.todos.create({ data: { text: todo } });
  revalidatePath('/todos'); // 重新验证 /todos 路径
}

4. 错误处理

Server Action 抛出的错误会被 React 捕获,但生产环境应避免泄露敏感信息。建议返回结构化的错误对象,而不是直接抛出。

总结

Server Actions 极大地简化了 React 全栈开发的数据变更流程。通过本文,你学会了:

  • 定义和调用 Server Actions
  • 结合 useFormState 和 useFormStatus 处理表单
  • 使用 useOptimistic 实现乐观更新
  • 输入验证与安全实践
  • 常见坑的规避方法

下一步,你可以探索:

  • 在 Server Actions 中访问数据库(如 Prisma、Drizzle)
  • 结合 cookies() 实现认证
  • 使用 revalidateTag 实现细粒度缓存失效

Server Actions 仍在快速发展,建议关注 React 和 Next.js 官方博客获取最新动态。

—

延伸阅读: