Skip to main content

第 16 章:19 新 Hooks——use / useOptimistic / useActionState / useFormStatus / useEffectEvent

React 19 带来了一批新 hook。它们不是把旧 hook 换个写法,而是补上了三类能力:渲染期读异步数据(use)、表单与 action 的状态管理(useActionState / useFormStatus / useOptimistic)、渲染期之外却需要稳定引用的回调(useEffectEvent)

先记住这一句:19 的新 hook 分三族:use 让你在渲染时读 promise 和 context;表单三件套管 action 的 state、status 和乐观更新;useEffectEvent 给 effect 一个"永远最新又不引发重跑"的回调。

一图流:19 新 hook 分三族

use:渲染期直接读 promise / context

useReactFiberHooks.js:1151)是 19 最"破格"的 hook:它可以在条件语句、循环里调用,不用遵守第 12 章的 hook 顺序规则。原因很简单:它不占 hook 链表节点,而是用一个独立的 thenableState 记录。传 promise,渲染期就"挂起"等它;传 context,就等于 readContext。Suspense 的机制(第 26 章)就是靠它把"等异步"接进渲染。

const data = use(fetchData()); // 挂起,Suspense 显示 fallback
const theme = use(ThemeContext); // 读 context,等价 readContext

表单与 action 三件套

这三个 hook 是配套 <form action> 用的(第 31 章),内部都跟"action 的异步状态"打交道。

useActionStatemountActionState):把 action 包装成"带 state 的版本"。它的实现里其实藏了两个 state hook:一个存 action 的返回值(就是你的 [state, action, pending] 里的 state),一个用乐观机制存 pending(2384dispatchOptimisticSetState)。action 跑完,state 更新,组件重渲染。

useFormStatusReactDOMFormActions.js:67)是所有 hook 里"最短"的一个,就两行:

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

它只是把调用转发给 dispatcher 的 useHostTransitionStatus,让 reconciler 返回最近的 form{pending, data, method, action}。它不需要参数,因为它读的是"离我最近的 form",这个信息靠内部机制定位。

useOptimisticmountOptimistic):乐观更新。它基于第 13 章那个双缓存差异:baseState 是"服务器确认的值",memoizedState 是"我乐观假设的值"。调用 setOptimistic 时立刻在 memoizedState 上改(界面秒变),action 完成后再把 baseState 对齐。

useEffectEvent:永远最新的稳定回调

useEffectEvent(源码里叫 mountEvent2723)解决的是 effect 闭包的老难题。它的实现把回调存在 ref = {impl: callback} 里:

const ref = {impl: callback};
hook.memoizedState = ref;
return function eventFn() {
return ref.impl.apply(undefined, arguments); // 每次都读最新的 impl
};

eventFn 引用稳定(deps 数组里永远一样),但调用时通过 ref 读最新的回调,所以不会拿到旧闭包。这就绕开了"把函数加进 effect deps 导致无限重跑"的死结。它还带一道保险:渲染期间调用直接抛错("A function wrapped in useEffectEvent can't be called during rendering."),逼你把副作用挪到正确时机。

动手实验

  1. use 读 promiseconst p = fetchData(); <Suspense fallback={...}><Comp/></Suspense>,在 use(ReactFiberHooks.js:1151)打断点,看它怎么把 promise 挂到 Suspense 上。
  2. useFormStatus 转发:在 useFormStatus(ReactDOMFormActions.js:67)打断点,看它怎么转发到 useHostTransitionStatus,以及返回值 {pending, data} 在 form 提交时的变化。
  3. useEffectEvent 最新值:一个 5 秒的 interval,每秒调 eventFn(),中途改 props,观察它是不是永远读到最新值(对照源码里 ref.impl)。

小结

  • use:渲染期读 promise / context,不占 hook 链表,所以可条件调用。
  • useActionState:action + state + pending 三合一,内部是两个 state hook。
  • useFormStatus:转发到 useHostTransitionStatus,读最近 form 的 status。
  • useOptimistic:利用 base / memoized 双缓存差异做乐观更新。
  • useEffectEventref.impl 永远最新,回调引用稳定,渲染期调用会报错。
  • 第三部分到此收尾。下一部分 commit 阶段,看这些 hook 挂上去的 effect 怎么被落地的。