如何理解 currentHook 和 workInProgressHook

54 阅读1分钟

以下 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 的对位就错开,后续全部错位,立刻触发数量或顺序的错误检查。