📰 来源: 博客园
React 19 引入的 useActionState 是近年来 React Hooks 体系中设计最精巧的 API 之一。它表面上只是一个管理表单状态的 Hook,但内部却隐藏着三 Hook 协作、循环队列调度、Transition 上下文恢复、Thenable 状态追踪等一系列精妙的工程实现。我们将从源码出发,逐层剥开它的架构设计,帮助我们真正理解这个 API 背后的设计哲学。
一、为什么 useActionState 值得深入分析?
在 React 19 之前,处理一个带有异步提交、loading 状态、错误处理的表单,我们需要这样写:
function OldForm() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [result, setResult] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError(null);
try {
const formData = new FormData(e.target);
const res = await submitToServer(formData);
setResult(res);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input name="email" />
<button disabled={isLoading}>
{isLoading ? '提交中...' : '提交'}
</button>
{error && <p className="error">{error}</p>}
{result && <p className="success">{result.message}</p>}
</form>
);
}
三个 useState、一个 try/catch/finally、一个 e.preventDefault()——这是每一个 React 开发者都写过无数遍的样板代码。而 React 19 给出的答案是:
function NewForm() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const res = await submitToServer(formData);
return { success: true, message: res.message };
},
{ success: null, message: '' }
);
return (
<form action={formAction}>
<input name="email" />
<button disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
{state.message && (
<p className={state.success ? 'success' : 'error'}>
{state.message}
</p>
)}
</form>
);
}
一个 Hook,三个返回值,零样板代码。这不是简单的语法糖——它背后是一套完整的 Action 驱动状态管理架构。理解了 useActionState,我们就理解了 React 19 对"副作用即状态"这一理念的全部思考。
二、从 useFormState 到 useActionState
useActionState 的前身是 React Canary 版本中的 useFormState。React 团队在正式发布时将其重命名,这个决策背后蕴含着深刻的设计思考。
useFormState 的问题在于:它把自己框死了。 "Form" 这个词暗示它只能用于表单场景,但实际上这个 Hook 的能力远不止于此。任何需要"执行一个副作用,然后基于结果更新状态"的场景,都可以用它来处理。React 团队意识到了这个命名上的局限,做出了一个看似微小实则关键的决定——将其更名为 useActionState。
这个改名反映了 React 19 的一个核心设计理念:Action 不只是表单的专利,它是一种通用的异步状态变更模式。 在 React 19 的语义体系中,"Action"指的是任何可能产生副作用并导致状态变更的函数调用。它可以是表单提交、按钮点击、数据同步,甚至是一个定时器触发的操作。useActionState 是这个 Action 体系的基础设施之一,与 useTransition、useOptimistic、useFormStatus 共同构成了完整的 Action 工具链。
从 useFormState 到 useActionState 的演进,本质上是从"数据驱动"到"意图驱动"的范式转变。前者关注的是"表单有什么数据",后者关注的是"用户想做什么"。这种视角的转换,让 API 的抽象层级提升了一个维度。
三、API 表面:简洁之下的设计
function useActionState<S, P>(
action: (state: Awaited<S>, payload: P) => Awaited<S> | Promise<Awaited<S>>,
initialState: Awaited<S>,
permalink?: string
): [Awaited<S>, (payload: P) => void, boolean];
这个签名中有几个值得注意的设计细节:
泛型 <S, P> 的双参数设计。S 代表状态类型,P 代表 payload 类型。S 被 Awaited<> 包裹,意味着状态可以是 Promise<T> 类型——Action 返回的 Promise 会被自动解包。这个设计让同步和异步 Action 在类型层面保持统一。
action 的第一个参数是 prevState。这和 useReducer 的 reducer 函数签名一脉相承,但有一个关键区别:useReducer 的 reducer 是纯同
🔗 原文链接: 点击阅读原文
文章评论