Server Actions —— 前后端交互的未来形态
概述
Server Actions 是可以直接从客户端代码(尤其是 Client Components)中调用的异步函数,但它们的函数体本身只在服务器上执行。
在这种模式下,你不会在浏览器 DevTools 的 Network 面板中看到任何请求,因为请求由服务器帮你完成了。这个看起来是多加了一层服务器在中间转发,不过也会带来几项优点:
- 服务器会自动缓存,大量客户端使用同一份数据的时候,不会多次请求;
- 发给客户端的都是已经渲染好的页面,这对 SEO 极其有利;
- 这种模式下,我们甚至可以直接操作数据库一步完成变更或者执行其他副作用,这在利用 NextJS 直接全栈开发的情况下尤其有利;
- 在某些禁用 JS 的情况下,它的渐进增强的性质使得出乎意料的仍能工作。
本质
它们是建立在 Web Fundamentals(特别是 HTML <form>)之上的一个 RPC (远程过程调用) 的抽象。你感觉像是在调用一个本地函数,但 Next.js 在底层为你处理了网络请求、数据序列化和反序列化。
优势
- 零 API 路由: 你不再需要为了一个简单的创建或更新操作去手动创建
/api/目录下的文件。你的业务逻辑和数据变更函数可以放在任何地方(通常是和服务端代码放在一起)。 - 简化数据变更: 你可以直接在 Server Action 内部调用数据库或服务层函数,修改数据,然后告诉 Next.js 哪些数据需要刷新。
- 无缝的数据刷新: 通过
revalidatePath或revalidateTag,你可以精确地让 Next.js 重新获取特定页面的数据并更新 UI,无需客户端手动 refetch。 - 渐进增强 (Progressive Enhancement): 这是一个非常优雅的特性。如果用户的浏览器禁用了 JavaScript,一个绑定了 Server Action 的
<form>仍然可以作为标准 HTML 表单工作,实现全页刷新提交。当 JavaScript 可用时,Next.js 会接管它,实现无刷新的局部更新。
实战演示
定义 action 函数
我会用一个简单的 Todo List 来演示怎么使用。
先定义一个用 fetch 封装的请求函数,这个通常会在 @/services 文件夹里,但是我采用 Ducks 风格组织代码,所以:
src\features\todos\todoServices.ts :
1 | export const addTodo = async (content: string) => { |
接着,我来创建真正用于 server action 的函数。这个函数,本质上就是基于上面的函数多一层封装。
这个封装出来的函数,能直接用于 <form action = { ... } > 标签的 action 属性。
众所周知,如果直接在 action 属性里填写 URL,HTML 的默认行为会自己请求这个地址;然而,React 和 NextJS 对这个行为进行了原生的增强。
现在,你可以往里面传一个函数,React 还会自动把 form 表单里的数据填入这个函数,作为其形参。我们现在就来定义这个函数:
src\features\todos\todoActions.ts
1 | export const createTodo = async ( |
核心就 3 点:
- 函数的形参是表单数据,React 自动填入。
- 函数定义处用了 use server,因为这个函数是在服务端执行
- 完成之后要
revalidatePath("..."),当我们调用它时,Next.js 会做两件事:- 清除服务端的数据缓存: 它会使 getTodos 在下次被调用时,必须重新 fetch 数据,而不是使用缓存
- 触发 RSC 重新渲染: Next.js 知道 / 路径的数据“脏了”,它会重新在服务器上运行 Home 这个 Server Component。Home 内部会再次调用 getTodos,获取到包含了新 Todo 的列表,然后将新的 RSC Payload 发送给浏览器。浏览器端的 React 会高效地 diff 并更新 DOM,于是新添加的 Todo 就出现在了页面上!
这个函数目前还出奇的简单,其实我们可以在里面加一些服务端可以做的事情,比如验证之类的,下文中我会体现。
使用 action 函数
简单到难以置信,这里直接贴代码了
1 | // components/add-todo-form.tsx |
我们没有写一行 fetch 的客户端代码,没有写一行 useState 来管理列表状态,没有写任何 useEffect 来同步数据,就实现了这个核心功能。这就是 Server Actions 的强大之处。
使用 useActionState 和 useFormStatus HOOK 进一步增强
[!warning] 请区分服务端和客户端(浏览器)
类似 TanStack Query 以及 React Form Hook 都有自己的 state 机制,很多时候在进行用户提示或者状态管理的时候会感到无从下手,我到底该用谁?!
请注意,本文中所有的校验、状态等,都是在反应服务器端的情况,因为这是配合 server action 用的。
在客户端可以进行一次校验,服务端再进行一次校验。state 作用的层级和位置是不同的,需要区分。
这一切很现代,很棒,但是还并没有友好的状态管理和错误处理机制。
下一步,我们需要使用两个 hook,这两个是 server action 的最佳搭档,我们的目标是在用户执行 add todo 成功或者失败的时候,都能用 toast 弹出一条友好的提示。
使用 useFormStatus hook
一个有趣的事实:
useFormStatus 现在是 “React DOM” 包里唯一一个 hook 了。
useFormStatus 的用法很简单,我直接用一个 submit button 举例:
1 | "use client"; |
其实就是从中解构出一个 pending 属性,这个在处理 loading 状态的时候很有用。
注意点:
useFormStatus hook 要求使用其的组件在 form 标签里
这个 hook 能很方便的创建出高度可复用的提交按钮这样的组件,而不依赖父组件表单的状态管理。
使用 useActionState hook
在早期的 React Canary 版本中,此 API 是 React DOM 的一部分,称为
useFormState,可见:useActionState – React 中文文档
useActionState 的签名大约长这样:
1 | const [state, action, isPending] = useActionState( |
其中的 actionFunction 其实就是我们上面定义的 server action 函数,而解构出的 action 是一个增强过的函数,我们需要把它替换掉原来的 actionFunction 给 form 表单用。
这个 hook 本质是为 server action 提供一个状态机,其解构出的另外两样东西也很有用:
-
state 结构由我们自己定义,useActionState 会自动帮我们管理这个 state ,接下来我会详细说这件事;
-
isPending 属性的作用其实和上面 useFormStatus 解构出来的的 pending 没啥区别,但是为了抽象出可复用的组件,我宁愿用 useFormStatus。
定义好 state 结构
要管理 state,我们得先有 state
src\features\todos\todoActions.ts
1 | export interface FormState { |
其中最主要就是 type 和 message 字段,前者标识状态类型,这个用于给用户提供友好的提示;
fieldErrors 用格式化输出详细报错信息,这个是给 zod 用的,能优雅的在出错时精确返回是哪一条出问题了。
formValues 用于数据的回填,想象一下用户提交信息失败了,这可能是因为网络原因之类的,用户并不想重新输入一次信息,所以你得把它出错的信息回填进去。
[!note]- 只在提交成功的时候手动清空表单,不就可以了吗?为什么要多此一举
这是因为提交后清空表单数据是 HTML 元素的默认行为。通常,我们会阻止此默认行为,但是由于 server action 本身又是利用了其默认行为,所以这里会很矛盾,显得不怎么优雅。不过目前来看,手动回填只能是不得已的方案了。
修改 server action 函数,增加返回值为 state
下面是完整的,修改好的 server action 函数。值得注意的是,这次我们设置了
- prevState 形参
- 以 state 为结构的返回值
提供给 useActionState 使用。
代码里还多了一些 zod 的操作,请注意,此时 zod 的验证发生在服务端,因为这是服务端函数。这恰恰体现了 zod 的强大:双端同步校验。
src\features\todos\todoActions.ts
1 | export const createTodo = async ( |
在组件里使用 useActionState hook
在这里,我们利用 useEffect 消费 useActionState 返回的 state 信息,来实现我们需要的友好的用户提示功能。
1 | const initialState: FormState = { |
- 标题: Server Actions —— 前后端交互的未来形态
- 作者: 三葉Leaves
- 创建于 : 2025-09-05 00:00:00
- 更新于 : 2026-01-13 15:50:33
- 链接: https://blog.oksanye.com/fbbe337a1649/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。