Appearance
第8章 React 19 新 Hooks 与 API
本章要点
- use Hook 的革命性设计:在条件语句和循环中调用的第一个 Hook
- useActionState 的工作机制:表单状态与异步 Action 的桥梁
- useFormStatus 的实现原理:跨组件读取表单提交状态
- useOptimistic 的乐观更新模型:即时反馈与最终一致性
- Actions 的内核机制:Transition 与异步函数的融合
- ref 作为 prop 的变革:forwardRef 的终结
- React 19 API 变更的源码级解读
React 19 是自 Hooks 引入以来最重大的一次 API 更新。它不仅增加了 use、useActionState、useFormStatus、useOptimistic 等新 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;
}
}
}