数据修改与 Server Actions
Next.js 使用 React 的 Server Functions 来处理写入。带有 use server 指令的异步函数可以被客户端通过网络调用。当它和 <form> 或 formAction 搭配时,就是大家常说的 Server Action。
创建 Server Function
在文件级别放 'use server',导出的函数都会变成 Server Function:
// app/actions.ts
'use server';
import { auth } from '@/lib/auth';
export async function createPost(formData: FormData) {
const session = await auth();
if (!session?.user) throw new Error('Unauthorized');
const title = formData.get('title');
const content = formData.get('content');
// 写数据库…
}
安全警告:Server Function 会暴露成一个 POST 端点,必须在函数内自行做鉴权与授权,不能只依赖 UI 层的按钮显示与否。
也可以在函数顶部写 'use server',仅标记单个函数:
export default function Page() {
async function createPost(formData: FormData) {
'use server';
// …
}
return <form action={createPost}>…</form>;
}
在表单中调用
React 扩展了 <form> 的 action prop,可以直接传 Server Function:
import { createPost } from '@/app/actions';
export function Form() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button type="submit">发布</button>
</form>
);
}
- 即便 JS 还没加载,表单也能提交(渐进增强)。
- 提交后,Next.js 会在同一次请求里返回更新后的 UI。
在事件里调用
客户端组件里用 onClick 等事件调用:
'use client';
import { incrementLike } from './actions';
import { useState } from 'react';
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
return (
<button onClick={async () => setLikes(await incrementLike())}>
❤ {likes}
</button>
);
}
处理期望内的错误
对于表单校验这类「可预期错误」,不要 throw,直接把错误当 返回值 返回:
'use server';
export async function createPost(prevState: any, formData: FormData) {
const res = await fetch('https://api.vercel.app/posts', {
method: 'POST',
body: JSON.stringify({
title: formData.get('title'),
content: formData.get('content'),
}),
});
if (!res.ok) return { message: '创建失败' };
return { message: '' };
}
在客户端用 useActionState 管理:
'use client';
import { useActionState } from 'react';
import { createPost } from '@/app/actions';
export function Form() {
const [state, formAction, pending] = useActionState(createPost, { message: '' });
return (
<form action={formAction}>
<input name="title" required />
<textarea name="content" required />
{state?.message && <p aria-live="polite">{state.message}</p>}
<button disabled={pending}>发布</button>
</form>
);
}
写入后刷新 UI
提交完通常需要刷新数据。按需选择:
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
// 写数据…
revalidatePath('/posts'); // 按路径失效
// revalidateTag('posts'); // 按 tag 失效
redirect('/posts'); // 重定向
}
revalidatePath/revalidateTag:让缓存失效,下次访问拉最新数据。refresh()(来自next/cache):刷新客户端路由,但不会让 tag 缓存失效。redirect():跳转到新页面,后续代码不会执行。
操作 Cookie
'use server';
import { cookies } from 'next/headers';
export async function signIn() {
const store = await cookies();
store.set('token', 'xxx'); // 设置
store.get('token')?.value; // 读取
store.delete('token'); // 删除
}
Next.js 在 Server Action 设置 cookie 后会自动重新渲染当前页与布局,让 UI 跟上。