学习路径
高级
React 19 Actions:把提交流程写成“契约”
在真实产品里,表单/提交几乎总会遇到同一组需求:pending、禁用、错误提示、成功反馈、重试、以及(可能的)乐观更新。\nReact 19 的 Actions 方向是把这套流程从“到处散落的状态”收敛成一个可组合的模型。
TL;DR
- Actions 的核心不是“少写几行代码”,而是把 提交 变成可组合的边界:输入 → pending → 成功/失败。\n-
useActionState适合把提交写成一个可验证的状态机。\n-useFormStatus适合让表单子树感知 pending,而不把状态层层透传。\n- 渐进增强取决于你的运行环境:只有当表单可以在“无 JS”时也能由服务端处理时,它才是真正的渐进增强。
1) 先明确:我们要解决什么“契约问题”
传统写法里,你通常会写出:isSubmitting、error、success、onSubmit、try/catch、以及各种禁用/提示逻辑。\n问题不是写不出来,而是:
- 契约不清晰:哪些输入会失败?失败时 UI 要保证什么?\n- 边界不稳定:提交逻辑分散在组件/Hook/工具函数里,难复用。\n- 状态易失控:并发提交、快速重复点击、取消/重试会把状态机搞乱。
React 19 的 Actions 提供的是一个更“结构化”的抽象:让“提交”成为 UI 的一部分,并且有明确的 pending 传播机制。
2) 最小模型:Action 是什么?
你可以把 Action 视为:一个接收 FormData 的函数(通常是 async),并把结果返回给 UI。
TypeScript
async function updateProfile(formData: FormData) {
const name = String(formData.get('name') ?? '');
if (name.trim().length < 2) {
return { ok: false, message: '姓名至少 2 个字符' };
}
await fakeNetwork();
return { ok: true };
}
function ProfileForm() {
return (
<form action={updateProfile}>
<input name="name" />
<button type="submit">保存</button>
</form>
);
}
读法
“form 的 action 不是 URL,而是函数”。你把“提交”声明在表单结构里,而不是在某个按钮点击回调里手写一套状态机。
3) 用 useActionState 把提交变成可验证的状态机
当你需要把“结果/错误/下一步提示”反馈给 UI,useActionState 是更适合的抽象:它让 action 接收 prevState 并返回 nextState。
TypeScript
import { useActionState } from 'react';
type FormState =
| { status: 'idle' }
| { status: 'success' }
| { status: 'error'; message: string };
async function submit(prevState: FormState, formData: FormData): Promise<FormState> {
const email = String(formData.get('email') ?? '');
if (!email.includes('@')) return { status: 'error', message: '邮箱格式不正确' };
await fakeNetwork();
return { status: 'success' };
}
export function EmailForm() {
const [state, formAction, isPending] = useActionState(submit, { status: 'idle' });
return (
<form action={formAction}>
<input name="email" inputMode="email" disabled={isPending} />
<button type="submit" disabled={isPending}>
{isPending ? '提交中…' : '提交'}
</button>
{state.status === 'error' && <p role="alert">{state.message}</p>}
{state.status === 'success' && <p>已保存</p>}
</form>
);
}
async function fakeNetwork() {
await new Promise((r) => setTimeout(r, 800));
}
为什么这是“深度提升”
你不再把 isSubmitting/error/success 当作松散变量,而是明确一个有限状态机。\n这会显著降低并发提交、重试、以及错误恢复时的复杂度。
4) 用 useFormStatus 让 pending 自动下沉
你经常希望“某个提交按钮/某块表单区域”知道表单是否 pending,但又不想把状态一层层传下去。\nuseFormStatus 解决的就是这个传播问题。
TypeScript
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '提交中…' : '提交'}
</button>
);
}
5) 渐进增强:哪些场景是真的“无 JS 也能工作”
渐进增强不是 React 单方面决定的,它依赖你的“运行环境”:
- 客户端 SPA:如果没有服务端接收表单提交,action 函数无法在无 JS 下运行。\n- 带服务端能力的框架:表单可以回退到传统提交,服务端执行 action 并返回结果,JS 只是增强体验。
把这一点讲清楚非常重要:否则读者会以为 Actions 是“纯前端就能自动渐进增强”的魔法。
6) 常见坑(Actions 经常踩的不是 API)
- 把业务流程写进 UI 状态:pending 只是状态,不等于业务完成\n2. 错误不可恢复:错误 UI 没有“重试”路径\n3. 并发提交未定义:重复点击/多次提交,状态机被覆盖\n4. 验证与提交混在一起:校验策略不清晰,错误提示不一致\n
下一步
- API 契约:/reference/react/use-action-state、/reference/react/use-form-status\n- 需要表单工程化建议:/guides/forms\n- 想把“提交”与并发体验结合:/reference/react/use-transition
动手练习
用 useActionState 管理提交状态:pending 禁用、错误与成功反馈。
🌐浏览器运行