Skip to content

第8章 React 19 新 Hooks 与 API

本章要点

  • use Hook 的革命性设计:在条件语句和循环中调用的第一个 Hook
  • useActionState 的工作机制:表单状态与异步 Action 的桥梁
  • useFormStatus 的实现原理:跨组件读取表单提交状态
  • useOptimistic 的乐观更新模型:即时反馈与最终一致性
  • Actions 的内核机制:Transition 与异步函数的融合
  • ref 作为 prop 的变革:forwardRef 的终结
  • React 19 API 变更的源码级解读

React 19 是自 Hooks 引入以来最重大的一次 API 更新。它不仅增加了 useuseActionStateuseFormStatususeOptimistic 等新 Hook,还从根本上改变了 React 处理异步操作和表单交互的方式。

如果说 React 16.8 的 Hooks 解决了"函数组件如何拥有状态"的问题,那么 React 19 的新 API 则瞄准了一个更大的目标:如何优雅地处理数据变更(mutation)。在此之前,React 一直擅长的是数据展示——从 state 到 UI 的单向流动。而数据的写入、提交、乐观更新等操作,长期以来都需要开发者自行搭建脚手架。React 19 将这些模式内建到了框架核心中。

8.1 use:打破 Hook 规则的 Hook

use 是 React 19 引入的最具颠覆性的 API。它打破了 Hooks 系统自诞生以来最核心的规则——它可以在条件语句和循环中被调用。

8.1.1 use 的两种模式

use 有两种截然不同的使用方式:读取 Promise 和读取 Context。

typescript
// 模式 1:读取 Promise
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  // 如果 Promise 还没 resolve,会挂起组件(触发 Suspense)
  const comments = use(commentsPromise);
  return comments.map(c => <p key={c.id}>{c.body}</p>);
}

// 模式 2:读取 Context(可以在条件语句中)
function ThemeButton({ showIcon }: { showIcon: boolean }) {
  if (showIcon) {
    const theme = use(ThemeContext); // ✅ 在条件中调用 use
    return <Icon color={theme.primary} />;
  }
  return <button>Click me</button>;
}

8.1.2 use(Promise) 的内核实现

use 接收一个 Promise 时,它的行为与 Suspense 紧密耦合:

typescript
function use<T>(usable: Usable<T>): T {
  if (usable !== null && typeof usable === 'object') {
    if (typeof (usable as Thenable<T>).then === 'function') {
      // Promise 路径
      const thenable = (usable as Thenable<T>);
      return useThenable(thenable);
    } else if ((usable as ReactContext<T>).$$typeof === REACT_CONTEXT_TYPE) {
      // Context 路径
      const context = (usable as ReactContext<T>);
      return readContext(context);
    }
  }
  throw new Error('An unsupported type was passed to use()');
}

useThenable 是核心实现,它实现了"同步化异步"的魔法:

typescript
function useThenable<T>(thenable: Thenable<T>): T {
  const index = thenableIndexCounter;
  thenableIndexCounter += 1;

  if (thenableState === null) {
    thenableState = createThenableState();
  }

  const result = trackUsedThenable(thenableState, thenable, index);

  // 如果 result 是 SUSPENDED_THENABLE,说明 Promise 还没 resolve
  // React 会 throw 这个 thenable,触发 Suspense 边界
  if (
    currentlyRenderingFiber.alternate === null &&
    (workInProgressHook === null
      ? currentlyRenderingFiber.memoizedState === null
      : workInProgressHook.next === null)
  ) {
    // 初次渲染时 Promise 未完成:这是合法的 Suspense 场景
  }

  return result;
}

function trackUsedThenable<T>(
  thenableState: ThenableState,
  thenable: Thenable<T>,
  index: number
): T {
  const trackedThenables = thenableState;
  const previous = trackedThenables[index];

  if (previous === undefined) {
    // 第一次遇到这个 thenable
    trackedThenables[index] = thenable;

    switch (thenable.status) {
      case 'fulfilled':
        return thenable.value;
      case 'rejected':
        throw thenable.reason;
      default:
        // pending 状态:附加 then 回调
        const pendingThenable = thenable as PendingThenable<T>;
        pendingThenable.status = 'pending';

        pendingThenable.then(
          (fulfilledValue) => {
            if (thenable.status === 'pending') {
              const fulfilledThenable = thenable as FulfilledThenable<T>;
              fulfilledThenable.status = 'fulfilled';
              fulfilledThenable.value = fulfilledValue;
            }
          },
          (error) => {
            if (thenable.status === 'pending') {
              const rejectedThenable = thenable as RejectedThenable<T>;
              rejectedThenable.status = 'rejected';
              rejectedThenable.reason = error;
            }
          }
        );

        // 抛出 thenable,触发 Suspense
        throw thenable;
    }
  } else {
    // 之前已经追踪过
    switch (previous.status) {
      case 'fulfilled':
        return (previous as FulfilledThenable<T>).value;
      case 'rejected':
        throw (previous as RejectedThenable<T>).reason;
      default:
        // 仍在 pending,继续挂起
        throw previous;
    }
  }
}

基于 VitePress 构建