以下 react 源码版本为 18.3.0
结论
currentHook 就是旧的 hook,workInProgressHook 是新的 hook,hook 的更新依赖两者的比对
核心概念(摘自源码):
189:194:packages/react-reconciler/src/ReactFiberHooks.new.js
// Hooks are stored as a linked list on the fiber's memoizedState field.
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
- currentHook:指向“当前 fiber”(上一次渲染留下的 memoizedState 链表)的遍历游标。更新时用它按顺序读取旧的 hook 节点。
- workInProgressHook:指向“本次渲染的 WIP 链表”正在构建的位置。每调用一个 hook,就在 WIP 链表里追加/复用对应节点。
运行时配合方式:
- 首次渲染(mount):currentHook 为空,只用
mountWorkInProgressHook逐个创建节点,串成 WIP 链表。 636:655:packages/react-reconciler/src/ReactFiberHooks.new.js
function mountWorkInProgressHook(): Hook {
const hook = {..., next: null};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
- 更新(update):
updateWorkInProgressHook让两条链并行前进:用 currentHook 读旧节点,用 workInProgressHook 复用/克隆到新链。如果本帧少/多调用了 hook,就会抛出 “Rendered more/fewer hooks...”。 657:716:packages/react-reconciler/src/ReactFiberHooks.new.js
function updateWorkInProgressHook(): Hook {
// 取下一旧节点
const nextCurrentHook = currentHook === null
? currentlyRenderingFiber.alternate?.memoizedState
: currentHook.next;
// 取下一新节点
const nextWorkInProgressHook =
workInProgressHook === null
? currentlyRenderingFiber.memoizedState
: workInProgressHook.next;
if (nextWorkInProgressHook !== null) {
// 已有 WIP,复用它
workInProgressHook = nextWorkInProgressHook;
currentHook = nextCurrentHook;
} else {
// 克隆旧节点到新链;若旧节点不存在则抛出“Rendered more hooks...”
if (nextCurrentHook === null) {
throw new Error('Rendered more hooks than during the previous render.');
}
currentHook = nextCurrentHook;
const newHook = { ...currentHook, next: null };
workInProgressHook =
workInProgressHook === null
? (currentlyRenderingFiber.memoizedState = newHook)
: (workInProgressHook.next = newHook);
}
return workInProgressHook;
}
直观理解:
- 把上一帧的 hook 链表看作“旧表”(currentHook 遍历),本帧边读旧表、边写新表(workInProgressHook)。两根指针必须同步推进,才能保证同一位置的 hook 类型一致。
- 一旦某帧因为条件分支少/多调用一个 hook,currentHook 与 workInProgressHook 的对位就错开,后续全部错位,立刻触发数量或顺序的错误检查。