Skip to content

React 的 WorkInProgressHook

🕒 Published at:

前言

React 在数年前全面转向函数式编程,并引入了 hook 的概念。卡颂的 React 技术揭秘 是很多的前端开发者进一步了解 react 的入门资料,但随着 react 的迭代更新,其底层实现可能发生了一些变化。这个本篇文章即是在讨论 useCallbackuseMemo 的差异上,去阅读 react 底层实现源码上发现的一些问题。React 的源码很长,将分片去研究 react 的底层实现逻辑。

本篇所使用的源码为 2024年5月4日的 react 主分支的实现。

Hook 类型定义

typescript
export type Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: any,
  next: Hook | null,
};

相关变量声明

typescript
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.

// work-in-progress fiber,区分于work-in-progress hook。
let currentlyRenderingFiber: Fiber = (null: any);


// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.

// Hooks 被存储为 fiber 的 memoizedState 字段中的一个链表。
// current hook 列表是属于 current fiber 的列表。
// work-in-progress hook 列表是一个新的列表,将被添加到 work-in-progress fiber 中。
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;

mountWorkInProgressHook

typescript
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    // 列表中的第一个 hook
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    // 追加到列表的末尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

updateWorkInProgressHook

typescript
function updateWorkInProgressHook(): Hook {
  // This function is used both for updates and for re-renders triggered by a
  // render phase update. It assumes there is either a current hook we can
  // clone, or a work-in-progress hook from a previous render pass that we can
  // use as a base.
  // 这个函数既用于更新,也用于由渲染阶段更新触发的重新渲染。
  // 它假设有一个可以 clone 的 current hook,或者一个之前渲染的 work-in-progress hook,可以作为基础。
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate; // WIP Fiber 的 alternate 属性指向 current Fiber
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;  // WIP Fiber 的 memoizedState 属性指向 work-in-progress hook
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    // 有已经存在的 work-in-progress。复用它。
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.

    if (nextCurrentHook === null) {
      const currentFiber = currentlyRenderingFiber.alternate;
      if (currentFiber === null) {
        // This is the initial render. This branch is reached when the component
        // suspends, resumes, then renders an additional hook.
        // Should never be reached because we should switch to the mount dispatcher first.
        // 这是初始渲染。这个分支在组件挂起、恢复后,再渲染一个额外的 hook 时被触发。
        // 理论上永远不会被触发,因为我们应该先切换到 mount dispatcher。
        throw new Error(
          'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
        );
      } else {
        // This is an update. We should always have a current hook.
        // 这是更新。我们应该总是有 current hook。
        throw new Error('Rendered more hooks than during the previous render.');
      }
    }

    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null,
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list.
      // 这是列表中的第一个 hook。
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      // 追加到列表的末尾。
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}