Appearance
10.2 Actions:简化数据突变
在 React 19 之前,处理表单提交、数据请求等异步操作通常需要开发者手动管理多个状态,例如 isLoading、error 和 data。这种模式不仅繁琐,而且容易出错。React 19 引入的 Actions 机制,旨在从根本上简化这类数据突变(Data Mutation)操作。
Actions 的核心思想是将与异步操作相关的状态转换(如 pending、error、success)封装起来,让开发者可以更专注于业务逻辑本身。它通过两个新的 Hooks——useFormStatus 和 useActionState(曾用名 useFormState)——以及对 <form> 组件的增强来实现。
1. useFormStatus:感知表单提交状态
useFormStatus Hook 允许组件感知其所在的 <form> 的提交状态。它返回一个包含 pending 状态的对象,当表单正在提交时,pending 为 true。
这极大地简化了在提交过程中禁用按钮、显示加载指示器等常见 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:
- 一个标准的
useState或useReducer,用于存储和更新state。 - 一个专门的
useState,用于管理isPending状态。这个 Hook 使用了dispatchOptimisticSetState,这暗示了它与 React 的并发渲染和乐观更新(Optimistic Updates)紧密相关。 - 一个自定义的 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 函数会:
- 找到对应的
formFiber 节点。 - 通过
ensureFormComponentIsStateful确保该 Fiber 节点上存在一个状态 Hook,用于追踪表单状态。这解释了为什么useFormStatus能够工作。 - 调用
startHostTransition,在 Transition 中执行 Action。这会将HostTransitionContext的pending状态设置为true,从而通知所有useFormStatus的使用者。
总结
React 19 的 Actions 机制是对 React 数据突变模式的一次重大升级。它通过 useFormStatus、useActionState 和底层的事件插件,将异步操作的状态管理、UI 反馈和并发控制无缝地集成在一起。
- 设计思想:将数据突变过程中的状态(pending, error, success)抽象并自动化,开发者只需关注 action 的核心逻辑。
- 实现原理:
- 利用 Context API (
HostTransitionContext) 广播pending状态。 useActionState内部组合多个 Hook,分别管理state、isPending和action队列。- 通过 事件插件 (
FormActionEventPlugin) 拦截 DOM 事件,并将其与 React 的并发更新机制(Transition)连接起来。
- 利用 Context API (
这种设计不仅极大地提升了开发体验,也使得代码更加健壮和可维护,是 React 向更完善的应用框架演进的重要一步。