跳到主要内容

创建日期: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.jsmountActionState):

  1. 一个 state hook —— 存 state
  2. 一个 pending state hook —— 存 isPending,用 dispatchOptimisticSetState 乐观更新(和 transition 的 pending 一个套路);
  3. 一个 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 的关系

方案说明
useFormStateReact 19 之前(Canary 期)的名字,只返回 [state, dispatch]已废弃
useActionState正式版名字,多返回 isPending
useState + useTransition手写方案:useTransitionisPendinguseState 存结果;useActionState 就是它的封装

迁移 useFormState → useActionState 只需要:解构里多加一个 isPending,其余逻辑不变。

注意点

  • React 19 才可用,且需要 'use client'(服务端组件里不能用 Hook)。
  • action 每次提交都是顺序执行的,下一次拿到的 prevState 是上一次返回的 state。
  • isPending乐观更新(类似 transition),提交一开始就变 true,结束才变回 false
  • permalink 用于「渐进增强」:让无 JS 环境下表单也能回退到服务端提交,一般项目可忽略。

关联

  • useTransitionuseOptimistic 同属「异步状态」一族:useOptimistic 负责乐观 UI,useActionState 负责表单动作的状态。