React 19 在表单处理领域带来了两个重要的新 Hook:useActionState 和 useFormStatus。它们为表单提交过程中的状态管理提供了原生的声明式支持,让你无需手写大量的 loading、error 状态管理逻辑,也无需引入额外的库即可获得流畅的提交体验。
为什么需要这两个 Hook
传统的表单提交通常需要手动维护 isSubmitting、error、success 等多个状态,代码会迅速变得冗长且容易遗漏边界情况。例如,一个简单的登录表单:
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
await loginApi(email, password);
// 跳转或提示成功
} catch (err) {
setError(err.message);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
{/* ... */}
<button disabled={isSubmitting}>
{isSubmitting ? '登录中...' : '登录'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
每个需要异步提交的表单几乎都要重复这套模板。useActionState 和 useFormStatus 正是为了简化这种模式而设计的。
useActionState:统一管理 Action 状态
useActionState 是一个专门为 Server Actions 和表单提交动作设计的 Hook,它接收一个异步函数,自动管理该函数执行过程中的状态(数据、错误、pending 状态等),并提供一个触发该函数的 Action 函数。
基本用法:
const [state, formAction, isPending] = useActionState(
async (previousState, formData) => {
// 执行异步操作,如调用 Server Action 或 API
const result = await submitForm(formData);
return result; // 返回结果会成为新的 state
},
initialState // 初始状态
);
返回值:
state:当前的状态对象,初始值为initialState,每次 action 执行成功后由返回值更新。formAction:一个可以直接绑定到<form action>的函数,当表单提交时触发。isPending:布尔值,表示异步操作是否正在执行。
实战示例——登录表单:
import { useActionState } from 'react';
async function loginAction(prevState, formData) {
const email = formData.get('email');
const password = formData.get('password');
try {
const user = await loginApi(email, password);
return { success: true, user };
} catch (error) {
return { success: false, error: error.message };
}
}
function LoginForm() {
const [state, formAction, isPending] = useActionState(loginAction, {
success: false,
error: null,
user: null,
});
if (state.success) {
return <div>欢迎回来,{state.user.name}!</div>;
}
return (
<form action={formAction}>
<input type="email" name="email" required />
<input type="password" name="password" required />
<button type="submit" disabled={isPending}>
{isPending ? '登录中...' : '登录'}
</button>
{state.error && <p className="error">{state.error}</p>}
</form>
);
}
关键点:
- 不需要
preventDefault,直接使用<form action={formAction}>,符合 Web 标准。 isPending直接从 Hook 中获取,无需手动切换 loading 状态。- 状态(成功/失败)和提示信息都集中管理,组件逻辑更清晰。
useFormStatus:感知表单提交状态
useFormStatus 主要用于子组件中读取当前 <form> 的提交状态,而无需通过 Props 逐层传递。这在设计系统或复杂表单中尤其有用,比如一个提交按钮,它需要根据表单是否正在提交来显示不同的样式和文本,但又不想让每个使用该按钮的父组件都手动传入 isPending。
基本用法:
const { pending, data, method, action } = useFormStatus();
返回值:
pending:布尔值,当前表单是否正在提交。data:当前正在提交的FormData对象(仅在 pending 为 true 时有值)。method:表单的method属性('get' 或 'post')。action:绑定在表单上的action函数引用。
注意:useFormStatus 必须在某个 <form> 的子组件中才能读取到状态。
实战示例——自定义提交按钮:
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
// 在表单中使用
function MyForm() {
const [state, formAction] = useActionState(handleSubmit, {});
return (
<form action={formAction}>
<input name="title" />
<SubmitButton />
</form>
);
}
这样 SubmitButton 可以复用在任何表单中,自动感知提交状态,无需父组件额外传参。
两者结合使用的最佳实践
useActionState 管理整体表单提交的状态和逻辑,useFormStatus 则让任何深层的表单组件都能感知到提交状态,实现高度解耦。
更完整的示例——带乐观更新的评论表单:
import { useActionState, useFormStatus } from 'react';
import { addComment } from './actions';
function CommentForm({ postId }) {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const content = formData.get('content');
if (!content.trim()) {
return { error: '评论内容不能为空' };
}
await addComment(postId, content);
return { success: true };
},
{ success: false, error: null }
);
return (
<form action={formAction}>
<textarea name="content" placeholder="写下你的评论..." />
<SubmitCommentButton />
{state.error && <p className="error">{state.error}</p>}
{state.success && <p className="success">评论发布成功!</p>}
</form>
);
}
function SubmitCommentButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '发布中...' : '发布评论'}
</button>
);
}
注意事项与局限性
- 环境要求:
useActionState和useFormStatus是 React 19 新增的 Hook,需要配合 React DOM 19 使用。它们天然支持 Server Actions,但也完全可以在纯客户端表单中使用普通的异步函数。
- 与普通受控表单的兼容:当使用
action属性时,表单默认为非受控模式(通过FormData获取数据)。如果你习惯使用受控组件(value+onChange),可以继续使用onSubmit模式,但此时无法充分利用useFormStatus的自动感知能力(除非你手动包裹<form>)。React 19 推荐优先使用action模式来简化表单逻辑。
- 错误处理与重试:
useActionState的核心思想是“每次提交返回一个新状态”,因此错误信息也应通过返回值传递,而不是抛出异常。你可以在 action 函数内部统一处理异常,将其转化为状态对象返回。
- 乐观更新:虽然
useActionState可以结合useOptimistic实现乐观更新(见下一节),但它本身不内置乐观更新逻辑。你需要根据state和isPending手动渲染乐观 UI。
总结
useActionState 和 useFormStatus 让表单处理进入了一个新的阶段:代码更接近 Web 标准、状态管理更自动、组件复用更自然。它们与 Server Actions 深度集成,为 React 19 的全栈能力奠定了表单这一关键领域的基础。在开发新项目时,这两个 Hook 应当成为你处理表单的首选方案。