02. Server Actions 实战:告别 API Routes
在 Next.js 14 之前,提交表单的流程通常是:
- 创建一个 API Route (
pages/api/submit.ts)。 - 编写前端组件,使用
useState管理 loading 状态。 onSubmit中调用fetch('/api/submit')。- 处理响应,手动刷新数据。
Server Actions 让这一切变成了历史。你可以直接在组件里调用一个 运行在服务器端 的函数。
1. 什么是 Server Action?
Server Action 是一个 异步函数,标记了 'use server'。它可以直接在 Server Component 中定义,也可以在单独的文件中定义供 Client Component 调用。
它类似于 远程过程调用 (RPC),Next.js 自动帮你处理了 HTTP 请求的封装。
2. 基础用法:简单的表单提交
不需要 useState,不需要 axios。
// app/page.tsx (Server Component)
import { revalidatePath } from 'next/cache'
// 1. 定义 Action (必须是 async)
async function createTodo(formData: FormData) {
'use server' // ✨ 魔法指令:这个函数只在服务器运行
const title = formData.get('title')
// 直接操作数据库!
await db.todo.create({ data: { title } })
// ✨ 关键:告诉 Next.js 数据变了,重新生成当前页面的 HTML
revalidatePath('/')
}
export default function Page() {
return (
<form action={createTodo}>
<input name="title" type="text" className="border p-2" />
<button type="submit">Add</button>
</form>
)
}发生了什么?
- 这是一个标准的 HTML Form,即使浏览器禁用了 JS 也能工作(渐进增强)。
- 点击提交后,Next.js 自动发起一个 POST 请求。
- 服务器执行
createTodo,写入数据库。 revalidatePath触发,Next.js 重新渲染页面(包含最新的 Todo 列表),并把新 HTML 返回给浏览器。- 浏览器更新视图。
全程没有写一行客户端 JS 代码!
3. 进阶用法:状态反馈与错误处理 (useFormState)
在 Client Component 中使用 Server Actions,我们需要反馈(例如:保存成功提示、字段验证错误)。
这时需要配合 useFormState Hook (React DOM 库提供)。
Step 1: 定义 Action (actions.ts) 建议把 Action 放在单独文件,这样 Client 和 Server 组件都能用。
// app/actions.ts
'use server'
import { z } from 'zod' // 推荐用 zod 做验证
const schema = z.object({
email: z.string().email({ message: '邮箱格式不正确' }),
})
export async function subscribe(prevState: any, formData: FormData) {
// 模拟延迟
await new Promise(resolve => setTimeout(resolve, 1000))
const validated = schema.safeParse({
email: formData.get('email'),
})
if (!validated.success) {
return {
message: '验证失败',
errors: validated.error.flatten().fieldErrors,
}
}
// 存入数据库...
return { message: '订阅成功!' }
}Step 2: 组件调用 (SubscribeForm.tsx)
// app/SubscribeForm.tsx
'use client'
import { useFormState, useFormStatus } from 'react-dom'
import { subscribe } from './actions'
// 一个小组件,用来显示 Loading 状态
function SubmitButton() {
const { pending } = useFormStatus() // ✨ 自动感知父级 form 的提交状态
return (
<button disabled={pending} type="submit" className="bg-blue-500 text-white p-2">
{pending ? '提交中...' : '订阅'}
</button>
)
}
export default function SubscribeForm() {
// state 包含 action 返回的值 (message, errors)
const [state, formAction] = useFormState(subscribe, { message: '' })
return (
<form action={formAction} className="flex flex-col gap-4">
<div>
<input name="email" className="border p-2" placeholder="输入邮箱" />
{/* 显示错误信息 */}
{state.errors?.email && <p className="text-red-500">{state.errors.email[0]}</p>}
</div>
<SubmitButton />
{/* 显示全局消息 */}
<p>{state.message}</p>
</form>
)
}4. 为什么企业级项目要用 Server Actions?
- 类型安全:前后端共用 TS 类型,参数变了直接报错。
- 代码复用:同一个
createTodo函数,既可以给<form>用,也可以在 API Route 里调用,甚至在 CLI 脚本里调用。 - 安全性:Server Actions 本质是 POST 请求,Next.js 自动生成了 CSRF Token 放在闭包里,天然防御 CSRF 攻击。
- 无瀑布流:Action 执行完后,可以一次性返回更新后的 UI 数据,不需要客户端再次 Fetch。
5. 总结
- 简单的增删改查:直接用 Server Component +
<form action={fn}>。 - 需要交互反馈:用 Client Component +
useFormState。 - 别忘了 revalidate:修改数据后,一定要调用
revalidatePath或revalidateTag来刷新缓存。