750 字
4 分钟
React 的表单 Actions

一个函数就是一个动作#

function Comment() {
async function post(formData) {
await save(formData.get('text'))
}
return (
<form action={post}>
<textarea name="text" />
<button type="submit">发布</button>
</form>
)
}

<form> 的 action 现在除了 URL,也可以收一个函数。这个函数会拿到提交的 FormData,可以异步。

三件由 React 替你做的事:提交跑在一次过渡里(所以不用写 e.preventDefault());动作成功后,表单里非受控字段会自动重置;HTTP 方法固定为 POST,不再看你写的 method。

注意最后一条的边界:自动重置的是非受控字段。受控字段的值来自 state,重不重置由你决定。

与 onSubmit 的取舍#

官方在 <form> 参考页里把两者对比列了出来:

onSubmit 在所有版本都能用,能直接拿到提交事件,可以 preventDefault 后自己读数据,new FormData(e.target) 就够。

用 action 传函数则会额外得到:React 跟踪的提交中状态、抛出的错误自动送到最近的错误边界、可以配合 useActionState 与 useOptimistic,以及支持 Server Function。

一句话:只是想在提交时跑段代码,onSubmit 就够;要让 React 管「提交中」这件事,或者要让服务端参与,用 action。

一个按钮一个动作#

表单默认动作用 action,某个按钮想另走一套,就在按钮上写 formAction:

<form action={publish}>
<textarea name="content" />
<button type="submit" name="button" value="submit">发布</button>
<button formAction={saveDraft}>存草稿</button>
</form>

按钮的 name 与 value 也会进 FormData,所以动作函数里能知道用户点的是哪个按钮。

提交中的状态#

按钮要显示「提交中」,用 useFormStatus:

function Submit() {
const { pending } = useFormStatus()
return <button type="submit" disabled={pending}>{pending ? '提交中……' : '提交'}</button>
}

它必须写在表单内部的子组件里,所以在渲染表单的那个组件里直接调用是拿不到状态的。

错误怎么处理#

动作函数抛错时,把表单包在错误边界里,让边界显示兜底界面:

<ErrorBoundary fallback={<p>提交失败,请重试</p>}>
<form action={submit}>
{/* ... */}
</form>
</ErrorBoundary>

可预期的错误(校验没过、邮箱重复)不该抛,应该 return 成状态显示在界面上,那部分交给 useActionState。

让服务端参与#

把 action 换成一个 Server Function(标了 'use server' 的函数),表单在 JavaScript 还没加载时也能提交,这就是渐进增强。传参有两种写法:用隐藏字段放进表单,或者用 .bind(null, productId) 预先绑一个参数。

这一层通常由框架铺好,业务代码里直接写 action={serverAction} 即可。

四条边界#

action 里的数据只在提交那一刻取。需要在输入过程中就做校验或预览,那属于受控组件的活,见 受控组件与非受控组件。

方法是 POST,别指望用函数形式的 action 做 GET 语义的搜索表单。

表单项都要有 name,否则不会进 FormData。

同一时刻别让多个动作改同一份状态:useActionState 的队列是串行的,连点会依次排队执行。

第 30 篇,共 39 篇本文属于系列

React 的表单 Actions
https://www.choria.top/posts/react-form-actions/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时