Skip to content

10.2 Actions:简化数据突变

在 React 19 之前,处理表单提交、数据请求等异步操作通常需要开发者手动管理多个状态,例如 isLoadingerrordata。这种模式不仅繁琐,而且容易出错。React 19 引入的 Actions 机制,旨在从根本上简化这类数据突变(Data Mutation)操作。

Actions 的核心思想是将与异步操作相关的状态转换(如 pending、error、success)封装起来,让开发者可以更专注于业务逻辑本身。它通过两个新的 Hooks——useFormStatususeActionState(曾用名 useFormState)——以及对 <form> 组件的增强来实现。

1. useFormStatus:感知表单提交状态

useFormStatus Hook 允许组件感知其所在的 <form> 的提交状态。它返回一个包含 pending 状态的对象,当表单正在提交时,pendingtrue

这极大地简化了在提交过程中禁用按钮、显示加载指示器等常见 UI 模式。

文件定位packages/react-dom/src/ReactDOMFormActions.js

javascript
export function useFormStatus(): FormStatus {
  const dispatcher = resolveDispatcher();
  return dispatcher.useHostTransitionStatus();
}

useFormStatus 的实现非常简洁,它实际上调用了内部的 useHostTransitionStatus

文件定位packages/react-reconciler/src/ReactFiberHooks.js

javascript
function useHostTransitionStatus(): TransitionStatus {
  return readContext(HostTransitionContext);
}

其核心是 readContext(HostTransitionContext)。这意味着 useFormStatus 的值完全由 HostTransitionContext 提供。当一个表单 Action 开始时,React 会将 HostTransitionContext 的值更新为包含 pending: true 的对象;当 Action 结束时,再将其恢复。

这种设计的精妙之处在于,它将表单状态的追踪与 React 的并发特性(Transition)联系在一起,并通过 Context API 将状态广播给表单内的所有子组件。

2. useActionState:管理异步操作状态

useActionState 是 Actions 机制的核心,它是一个功能更强大的 Hook,用于管理执行异步操作(Action)时的状态。它接收一个 action 函数和初始状态,返回一个包含当前状态、分发函数和 pending 状态的数组。

typescript
const [state, dispatch, isPending] = useActionState(async (previousState, payload) => {
  // ... 异步操作
  return newState;
}, initialState);

useActionState 的实现比 useFormStatus 复杂得多,因为它内部封装了状态管理、pending 状态追踪和 action 的调度。

文件定位packages/react-reconciler/src/ReactFiberHooks.js (概念性实现)

javascript
function mountActionState<S, P>(
  action: (Awaited<S>, P) => S,
  initialState: Awaited<S>,
): [Awaited<S>, (P) => void, boolean] {
  // 1. 创建用于存储 state 的 Hook
  const stateHook = mountWorkInProgressHook();
  stateHook.memoizedState = stateHook.baseState = initialState;
  const stateQueue = { ... };
  stateHook.queue = stateQueue;
  const setState = dispatchSetState.bind(null, currentlyRenderingFiber, stateQueue);
  stateQueue.dispatch = setState;

  // 2. 创建用于存储 pending 状态的 Hook
  const pendingStateHook = mountStateImpl(false);
  const setPendingState = dispatchOptimisticSetState.bind(null, ...);

  // 3. 创建用于管理 action 队列的 Hook
  const actionQueueHook = mountWorkInProgressHook();
  const actionQueue: ActionStateQueue<S, P> = {
    state: initialState,
    dispatch: null,
    action: action,
    pending: null,
  };
  actionQueueHook.memoizedState = actionQueue;

  // ... 返回 state, dispatch, isPending
}

mountActionState 的结构可以看出,useActionState 巧妙地组合了三个内部 Hook:

  1. 一个标准的 useStateuseReducer,用于存储和更新 state
  2. 一个专门的 useState,用于管理 isPending 状态。这个 Hook 使用了 dispatchOptimisticSetState,这暗示了它与 React 的并发渲染和乐观更新(Optimistic Updates)紧密相关。
  3. 一个自定义的 Hook,用于存储 action 函数本身以及管理一个 action 队列。

这种设计将一个复杂的异步操作分解为三个可管理的部分,并通过 React 的底层 Hook 机制将它们协同工作,为开发者提供了简洁而强大的 API。

3. FormActionEventPlugin:连接 DOM 与 React

这一切的起点是 FormActionEventPlugin。这是一个事件插件,它会监听表单的 submit 事件。当表单提交时,它会拦截事件,并触发与该表单关联的 Action。

文件定位packages/react-dom-bindings/src/events/FormActionEventPlugin.js

javascript
function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  // ...
  if (domEventName === 'submit' && action !== null && action !== undefined) {
    // ...
    const formFiber = findForm(targetInst);
    if (formFiber !== null) {
      // 确保表单 Fiber 有状态 Hook
      const formStateHook = ensureFormComponentIsStateful(formFiber);
      // ...
      // 启动一个 Transition 来执行 action
      startHostTransition(formFiber, () => {
        // ...
      });
    }
  }
}

当表单提交时,extractEvents 函数会:

  1. 找到对应的 form Fiber 节点。
  2. 通过 ensureFormComponentIsStateful 确保该 Fiber 节点上存在一个状态 Hook,用于追踪表单状态。这解释了为什么 useFormStatus 能够工作。
  3. 调用 startHostTransition,在 Transition 中执行 Action。这会将 HostTransitionContextpending 状态设置为 true,从而通知所有 useFormStatus 的使用者。

总结

React 19 的 Actions 机制是对 React 数据突变模式的一次重大升级。它通过 useFormStatususeActionState 和底层的事件插件,将异步操作的状态管理、UI 反馈和并发控制无缝地集成在一起。

  • 设计思想:将数据突变过程中的状态(pending, error, success)抽象并自动化,开发者只需关注 action 的核心逻辑。
  • 实现原理
    • 利用 Context API (HostTransitionContext) 广播 pending 状态。
    • useActionState 内部组合多个 Hook,分别管理 stateisPendingaction 队列。
    • 通过 事件插件 (FormActionEventPlugin) 拦截 DOM 事件,并将其与 React 的并发更新机制(Transition)连接起来。

这种设计不仅极大地提升了开发体验,也使得代码更加健壮和可维护,是 React 向更完善的应用框架演进的重要一步。

Last updated: