创建日期:2026-09-03 | 最近更新:2026-09-03
useActionState:React 19 表单状态管理
它是什么
useActionState 是 React 19 引入的 Hook,用来管理「异步动作 + 表单」的状态:提交表单、跟踪 pending、拿到返回值,一个 Hook 全包了。
它是 useFormState 的升级替代(后者已废弃),最大的区别是多返回了一个 isPending。
一句话:以前「表单提交 + loading 态 + 提交结果」要自己用
useState+useTransition拼,现在useActionState把这一套内置了。
签名
const [state, formAction, isPending] = useActionState(
action, // (prevState, payload) => newState,可以是 async
initialState, // 初始 state
permalink?, // 可选:渐进增强用的表单 URL
);
参数与返回值(源码见 open/react/packages/react/src/ReactHooks.js):
export function useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean]
action(prevState, payload):接收「上一次的 state」和「本次 payload」,返回新的 state。可以是 async,返回的 Promise 会被自动await。initialState:初始 state。- 返回三元组:
元素 类型 含义 stateAwaited<S>当前 state(Promise 已解开) formAction(P) => void传给 <form action={formAction}>,表单提交时 React 会把FormData作为payload传进来isPendingbooleanaction 是否进行中
底层机制(源码速览)
useActionState 内部其实是 3 个 hook 的合体(ReactFiberHooks.js 的 mountActionState):
- 一个 state hook —— 存
state; - 一个 pending state hook —— 存
isPending,用dispatchOptimisticSetState乐观更新(和 transition 的 pending 一个套路); - 一个 action queue hook —— 存 action 函数和队列,提交时按顺序在事件里执行,而不是在 render 里执行。
state 存成「thenable」,渲染时由 use 算法解开——所以 async action 的返回能直接同步读到(React 会等它 resolve)。
用法
1. 表单 + Server Action(最典型)
服务端 action:
// actions.js
'use server';
export async function createUser(prevState, formData) {
const name = formData.get('name');
if (!name) return { error: '名字不能为空' };
await db.createUser({ name });
return { message: `已创建「${name}」` };
}
客户端组件:
// SignupForm.jsx
'use client';
import { useActionState } from 'react';
import { createUser } from './actions';
export default function SignupForm() {
const [state, formAction, isPending] = useActionState(createUser, { message: '' });
return (
<form action={formAction}>
<input name="name" placeholder="名字" />
<button type="submit" disabled={isPending}>
{isPending ? '提交中…' : '提交'}
</button>
{state?.message && <p>{state.message}</p>}
{state?.error && <p style={{ color: 'red' }}>{state.error}</p>}
</form>
);
}
要点:
formAction直接交给<form action={...}>,提交时 React 自动把FormData塞给createUser的第二个参数。isPending在提交期间为true,用来禁用按钮 / 显示 loading。action的返回值变成下一次渲染的state,用来展示成功/失败信息。
2. 非表单:手动调用 formAction
第二个返回值 formAction 本质是 (payload) => void,不接 <form> 也能手动调:
function Counter() {
const [count, increment, isPending] = useActionState(
async (prev) => prev + 1,
0,
);
return (
<button onClick={() => increment()} disabled={isPending}>
{count}
</button>
);
}
手动调用时
payload就是你自己传的参数(这里没传,是undefined);action只用到prev。
3. 带额外参数
action 的第二个参数不一定非得是 FormData,可以绑定自定义参数:
const [state, submit] = useActionState(
async (prev, { id }) => {
const ok = await approve(id);
return { ...prev, [id]: ok };
},
{},
);
// 手动传 payload
submit({ id: 42 });
代码示例:完整可跑的最小表单
'use client';
import { useActionState } from 'react';
async function loginAction(prevState, formData) {
const username = formData.get('username');
const password = formData.get('password');
// 模拟请求
const ok = await new Promise((r) => setTimeout(() => r(username === 'admin'), 500));
return ok ? { status: 'ok' } : { status: 'fail', message: '账号或密码错误' };
}
export default function Login() {
const [state, formAction, isPending] = useActionState(loginAction, { status: 'idle' });
return (
<form action={formAction}>
<input name="username" />
<input name="password" type="password" />
<button disabled={isPending}>{isPending ? '登录中…' : '登录'}</button>
{state.status === 'fail' && <p style={{ color: 'red' }}>{state.message}</p>}
</form>
);
}
与 useFormState / useTransition 的关系
| 方案 | 说明 |
|---|---|
useFormState | React 19 之前(Canary 期)的名字,只返回 [state, dispatch],已废弃 |
useActionState | 正式版名字,多返回 isPending |
useState + useTransition | 手写方案:useTransition 拿 isPending,useState 存结果;useActionState 就是它的封装 |
迁移 useFormState → useActionState 只需要:解构里多加一个 isPending,其余逻辑不变。
注意点
- React 19 才可用,且需要
'use client'(服务端组件里不能用 Hook)。 action每次提交都是顺序执行的,下一次拿到的prevState是上一次返回的 state。isPending是乐观更新(类似 transition),提交一开始就变true,结束才变回false。permalink用于「渐进增强」:让无 JS 环境下表单也能回退到服务端提交,一般项目可忽略。
关联
- 与
useTransition、useOptimistic同属「异步状态」一族:useOptimistic负责乐观 UI,useActionState负责表单动作的状态。