一句话定义
React 19(2024 年底稳定)把「异步提交」变成一等公民:Actions(<form action> + useActionState + useOptimistic + use())封装了 pending/错误/乐观更新的全套样板,同时 ref 可作普通 prop、文档元数据可直接写进组件、React Compiler 自动记忆化。
为什么重要
表单提交曾是 React 样板代码最密集的场景(state×3 + handler + 竞态 + 错误提示);Actions 把它压缩成「一个函数 + 两个 Hook」。同时本篇标记学习路线的「前沿」边界:这些 API 正在成为新项目的默认写法。
前置知识
核心概念
- Action:传给
<form action={fn}>的异步函数;React 自动管理 pending、错误边界语义与重置非受控表单。 - useActionState(fn, initState):包装 action,拿到
[state, formAction, isPending]——state 是上一次提交的返回值。 - useOptimistic(state, reducer):在异步完成前先显示「乐观结果」,完成后自动回滚/校正为真实状态。
- use(promise / context):在渲染期间读取 Promise/Context 的同步写法(配 Suspense)。
- ref 作为 prop:函数组件直接接收
ref,forwardRef 退居兼容层。 - 文档元数据:
<title>、<meta>可直接写在组件 JSX 里,React 自动提升到<head>。 - React Compiler:自动记忆化(useMemo/useCallback/memo 的大多数手写场景),19 时代逐步落地(以官方发布状态为准)。
原理 / 机制
旧写法 → Actions 的演进:
jsx// 旧:三 state + handler + 错误处理(kp-011 的完整负担)
const [name, setName] = useState('');
const [pending, setPending] = useState(false);
const [error, setError] = useState(null);
async function onSubmit(e) {
e.preventDefault();
setPending(true);
try { await submit(name); } catch (err) { setError(err); }
finally { setPending(false); }
}
// 新:Actions 一条龙
'use client';
import { useActionState } from 'react';
async function createAction(prev, formData) {
const name = formData.get('name'); // 非受控取值!
const err = await api.create(name); // 抛错/返回都行
return err ?? null; // 返回值成为新 state
}
function CreateForm() {
const [error, formAction, isPending] = useActionState(createAction, null);
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>{isPending ? '提交中…' : '创建'}</button>
{error && <p role="alert">{error.message}</p>}
</form>
);
}
乐观更新(点 like 立刻 +1,服务器失败自动回滚):
jsxfunction LikeButton({ postId, initial }) {
const [likes, setLikes] = useOptimistic(initial);
return (
<button onClick={async () => {
setLikes(l => l + 1); // 乐观显示(自动在请求结束后校正)
await api.like(postId); // 失败则回滚为真实值
}}>
👍 {likes}
</button>
);
}
Server Action(RSC 体系的上行通道,kp-024 的配对概念):
jsx// app/actions.js(服务端文件)
'use server';
export async function createPost(formData) {
'use server';
await db.post.create({ title: formData.get('title') });
revalidatePath('/posts'); // 通知客户端 RSC 缓存失效
}
// 任意 Client Component
<form action={createPost}>…</form> // 函数引用跨边界安全(由框架序列化)
直观类比
旧表单像手写报销单:你自己填三联单(三个 state)、自己跑流程(handler)、自己等章(pending)、盖错了自己撕(错误重置)。Actions 像自动报销系统:把单子丢进扫码口(form action),系统自动显示「处理中」(isPending)、失败自动退单(state 返回错误)、成功自动归档(表单重置);「乐观更新」则是先给你盖个「预计通过」的电子章,系统拒绝时章自动消失。
实例 / 案例
「表单 + 乐观列表」的组合(学习路径终局项目的核心片段):
jsx'use client';
function CommentList({ initialComments, postId }) {
const [comments, addComment] = useOptimistic(
initialComments,
(list, newOne) => [...list, newOne]
);
async function formAction(formData) {
const text = formData.get('text');
addComment({ id: Date.now(), text, pending: true }); // 乐观插入
await api.comment(postId, text); // 真实提交
}
return (
<>
<form action={formAction}>
<input name="text" required />
<button>发送</button>
</form>
<ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>
</>
);
}
零 loading state、零 error state、零手动重置——三条样板线全部消失,这就是 Actions 的价值量化。
常见误区
- 以为 Actions 只服务 RSC:Client 场景(纯 SPA 里的
<form action>)同样可用;Server Action 只是它的一种。 - useActionState 的 fn 签名记错:第一个参数是上一次的 state,第二个才是 formData——新手第一坑。
- 在 action 里 setState 混用旧模式:Actions 接管了 pending/错误语义,再手动 setState 会互相打架。
- 乐观更新不做服务端校验:useOptimistic 的回滚依赖真实结果;不要在失败时假装成功。
- 以为 Compiler 让 useMemo 全部可删:自动记忆化覆盖「组件内」场景,跨组件引用稳定性需求(如依赖自定义 Hook 的行为契约)仍需人工判断;迁移期以官方指引为准。
自测题
- 写出 useActionState 的三返回值与 fn 签名;「prev」参数的用途是什么?
- useOptimistic 的回滚机制依赖什么?失败场景下 UI 会经历哪两帧?
- Server Action 与 Client
<form action>的区别?revalidatePath 做了什么? - 「有了 Actions 就不需要受控表单了」——对吗?说出仍需受控的两种场景(提示:实时校验、字符过滤,kp-011)。
- ref 作为 prop 与 forwardRef 的关系?
与其他知识点的关系
- 向前:受控表单(kp-011)的范式对照;并发优先级(kp-022)的封装。
- 向后:kp-026 中 Next.js 的 Server Actions 是其完整工程形态;kp-028 审查清单新增「表单是否可用 Actions 简化」检查项;学习路径终局项目直接使用本篇 API。
延伸阅读
- React 19 发布公告:<https://react.dev/blog/2024/12/05/react-19>
- useActionState / useOptimistic / use API(官方参考):<https://react.dev/reference/react/useActionState>