🔥🔥🔥 React18 源码学习 - hook 原理

53 阅读3分钟

前言

本文的React代码版本为18.2.0

可调试的代码仓库为:GitHub - yyyao-hh/react-debug at master-pure7

React16.8引入Hook以来,它彻底改变了我们编写React组件的方式。Hook不仅让函数组件具备了类组件的能力,还带来了更优雅的代码组织和逻辑复用方式。本文将深入React18源码,全面解析Hook的实现机制,揭示其背后的设计思想和实现细节。

Hook 的定义

首先看几个常用hooks的定义。可以看出useStateuseEffect都是通过dispatcher对象去调用对应的方法

  • useState: dispatcher.useState
  • useEffect: dispatcher.useEffect
  • ...
/* react/packages/react/src/ReactHooks.js */

export function useState(...) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

export function useEffect(...) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

然后我们找到初始化定义的地方,dispatcher是通过ReactCurrentDispatcher.current取值的

/* react/packages/react/src/ReactCurrentDispatcher.js */

const ReactCurrentDispatcher = {
  current: (null: null | Dispatcher)
};

/* react/packages/react/src/ReactHooks.js */

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return ((dispatcher: any): Dispatcher);
}

那对于ReactCurrentDispatcher.current的赋值操作,我们得追溯到renderWithHooks函数(在构建Fiber树一文时有提到)。在渲染一个函数组件时,在调用组件函数本身之前,会先设置好正确的Dispatcher

依旧通过current === null来判断当前是初始化(HooksDispatcherOnMount)还是更新(HooksDispatcherOnUpdate),其中包含了各种hooks的操作

/* react/packages/react-reconciler/src/ReactFiberHooks.old.js */

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;

  // 调用函数组件
  let children = Component(props, secondArg);
  return children;
};

// 1. 初始化(Mount)
const HooksDispatcherOnMount: Dispatcher = {
  useEffect: mountEffect,
  useRef: mountRef,
  useState: mountState,
  ...
};

// 2. 更新(Update)
const HooksDispatcherOnUpdate: Dispatcher = {
  useEffect: updateEffect,
  useRef: updateRef,
  useState: updateState,
  ...
};

初始化时的 Hook

观察HooksDispatcherOnMount中的每一个处理函数,我们发现每个函数中都会调用mountWorkInProgressHook 函数

// react/packages/react-reconciler/src/ReactFiberHooks.old.js
const HooksDispatcherOnMount: Dispatcher = {
  useEffect: mountEffect,
  useRef: mountRef,
  useState: mountState
};

// useEffect
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = mountWorkInProgressHook();
  ...
};
function mountEffect(...) {
  return mountEffectImpl(...);
};

// useRef
function mountRef(...) {
  const hook = mountWorkInProgressHook();
  ...
};

// useState
function mountState(...) {
  const hook = mountWorkInProgressHook();
  ...
};

我们紧接着观察这个函数,

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null, // 用于存储当前的状态值
    baseState: null,     // 基础状态: 用于计算新的状态
    baseQueue: null,     // 基础队列: 存储被跳过的更新(并发模式下)
    queue: null,         // 更新队列: 存储所有要处理的状态更新 (最重要!)
    next: null,          // 指向下一个Hook的指针 (用于构成链表)
  };

  // 保存hook到链表上
  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

更新时的 Hook

观察HooksDispatcherOnUpdate中的每一个处理函数,我们发现每个函数中都会调用updateWorkInProgressHook函数

// react/packages/react-reconciler/src/ReactFiberHooks.old.js
const HooksDispatcherOnUpdate: Dispatcher = {
  useEffect: updateEffect,
  useRef: updateRef,
  useState: updateState
};

// updateEffect
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = updateWorkInProgressHook();
  ...
};
function updateEffect(...) {
  return updateEffectImpl(...);
};

// updateRef
function updateRef(...) {
  const hook = updateWorkInProgressHook();
  ...
};

// updateState
function updateReducer(...) {
  const hook = updateWorkInProgressHook();
  ...
};
function updateState(...) {
  return updateReducer(basicStateReducer, (initialState: any));
};

我们紧接着观察这个函数,

function updateWorkInProgressHook() {
  // 获取 current 树上的 Fiber 的 hook 链表
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

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

    if (nextCurrentHook === null) {
      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.
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}

总结

React Hook的实现展示了几个重要的软件设计原则:

  1. 链表数据结构:通过简单的链表结构高效管理组件状态
  2. 闭包的应用:利用闭包捕获状态和更新函数
  3. 函数式编程思想:纯函数与副作用分离
  4. 优先级调度:在并发模式下实现高性能更新

Hook的设计不仅解决了React开发中的实际问题,也为未来的React发展奠定了基础。理解Hook的实现原理,有助于我们编写更高效、更可靠的React应用。

下一章我们将详细的学习某个具体hook的实现原理