Skip to main content

第 31 章:Actions 与表单——useActionState 全流程

表单是 React 的老大难:提交、加载中、失败回滚、乐观更新,全靠手写 useState + useEffect 拼。React 19 用 Actions 把这套做成内建能力。第 16 章看过 useActionState/useFormStatus/useOptimistic 的注册,本章看它们怎么配合起来跑完一次表单提交。

先记住这一句:Actions 让「异步提交」成为一等公民:<form action> 触发,useActionState 管 state + pending,useFormStatus 读状态,useOptimistic 做乐观更新。三件套共享"这次 action 进行到哪"这条线。

一图流:一次表单提交的分工

form action:触发点

<form action={fn}> 是原生行为的增强:表单提交不再走"受控组件 + 事件 + 手动 fetch",而是直接调 fnfn 可以是个 Server Action,第 30 章的 use server)。提交动作会被识别成"一次 action 执行",React 负责调度它、跟踪它的状态、并把结果广播给所有相关 hook(entangleAsyncActionReactFiberAsyncAction.js:71 负责把 action 和它的 thenable 纠缠到 React 的调度里)。

useActionState:action 的 state + pending

mountActionStateReactFiberHooks.js:2356)内部其实塞了两个 state hook(第 16 章埋的伏笔):

const stateHook = mountWorkInProgressHook(); // 存 action 的返回值
stateHook.memoizedState = initialState;
stateHook.queue = stateQueue; // 用 dispatchSetState 更新

const pendingStateHook = mountStateImpl(false); // 存 pending
// pending 走 dispatchOptimisticSetState:乐观地立刻翻转

所以 const [state, formAction, pending] = useActionState(action, init) 的三元组:state 是 action 返回的结果(服务端数据、错误信息),formAction 是包装好的提交函数,pending 是"这次提交进行中"的布尔。action 完成,state 更新、pending 归 false,组件重渲染。

useFormStatus:读"离我最近的表单"

useFormStatus 就是一行转发(ReactDOMFormActions.js:67):dispatcher.useHostTransitionStatus()。它读的是最近的 <form> 的状态,所以它不用传参,表单在哪一层,它就读哪一层的 {pending, data, method, action}。典型用法:提交按钮放在子组件里,父组件用 useActionState,按钮用 useFormStatus 读 pending 来禁用/转圈,不用把 pending 一层层传下去。

useOptimistic:先乐观,后对齐

useOptimistic1949)做乐观更新:调用 setOptimistic(新值)立刻把 UI 改成"我预计会成功的样子";action 真正完成时,用服务端返回的真实值对齐。它吃第 13 章的双缓存差异:baseState 是"服务端确认的值",memoizedState 是"乐观假设的值",两个值差着的那段,就是 UI 提前显示的部分。

三件套的配合逻辑

提交按钮一按,一条线串起来:form action 触发 → entangleAsyncAction 把 action 挂进调度 → useFormStatus 读到 pending=true 挂 loading → useOptimistic 把列表先改成"已提交"的样子 → action 返回 → useActionState 的 state 拿到真实结果 → pending=false、乐观值对齐。你不用写一行 useEffect 管 loading,这是 Actions 和手写方案的本质区别。

动手实验

  1. 看 useActionState 的两个 hook:在 mountActionState(2356)打断点,数它挂了几个 hook(应该有两个:state + pending)。
  2. 看 useFormStatus 转发:在 useFormStatus(ReactDOMFormActions.js:67)打断点,确认它只转发到 useHostTransitionStatus,返回值随表单提交变化。
  3. 看乐观对齐useOptimistic 提交一个慢 action,观察 UI 先变(乐观值)再对齐(真实值),中间 base/memoized 差着。

小结

  • Actions = 异步提交内建化,<form action> 触发,entangleAsyncAction(71) 挂进调度。
  • useActionState(2356) 内部两个 state hook:返回值 + 乐观 pending。
  • useFormStatus(67) 一行转发,读最近 form 的状态,不用传参。
  • useOptimistic(1949) 用双缓存差异做"先乐观后对齐"。
  • 三件套配合,省掉手写 useEffect 管 loading。
  • 下一步:19 怎么处理 title、style、preload 这些文档级资源,第 32 章。