React Hooks 源码解读之Hook入口

1,028 阅读6分钟

react 版本:v17.0.3

Hook 是 React 16.8 的新增特性,它可以让我们在不编写 class组件 的情况下使用 state 以及其它的 React 特性。

1、hook 入口

首先我们在入口文件 packages/react/index.js 文件中找到对外导出的 hook:

// packages/react/index.js
export {

  // ...

  useCallback,
  useContext,
  useDebugValue,
  useDeferredValue,
  useEffect,
  useImperativeHandle,
  useLayoutEffect,
  useMemo,
  useMutableSource,
  useReducer,
  useRef,
  useState,
  
  // ...
  
} from './src/React';

可以看到,它们在 packages/react/src/React.js 导入了 ReactHooks文件的 hooks,然后对外导出:

// packages/react/src/React.js

// 从 ReactHooks 文件中导入 hooks
import {
  // ...
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useMutableSource,
  useReducer,
  useRef,
  useState,
  // ...
} from './ReactHooks';


// 对外导出 hooks
export {
  // ...
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useMutableSource,
  useReducer,
  useRef,
  useState,
  // ...
};

在 packages/react/src/React.js 文件中,我们可以看到 hooks 都是导自 packages/react/src/ReactHooks.js 文件:

// packages/react/src/ReactHooks.js 

export function useContext<T>(Context: ReactContext<T>): T {
  const dispatcher = resolveDispatcher();
  
  // 删除了 Dev 部分的代码
 
  return dispatcher.useContext(Context);
}

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

export function useRef<T>(initialValue: T): {|current: T|} {
  const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useLayoutEffect(create, deps);
}

export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}

export function useImperativeHandle<T>(
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useImperativeHandle(ref, create, deps);
}

export function useDebugValue<T>(
  value: T,
  formatterFn: ?(value: T) => mixed,
): void {
  if (__DEV__) {
    const dispatcher = resolveDispatcher();
    return dispatcher.useDebugValue(value, formatterFn);
  }
}



export function useTransition(): [boolean, (() => void) => void] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useTransition();
}

export function useDeferredValue<T>(value: T): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useDeferredValue(value);
}

export function useOpaqueIdentifier(): OpaqueIDType | void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useOpaqueIdentifier();
}

export function useMutableSource<Source, Snapshot>(
  source: MutableSource<Source>,
  getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
  subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
): Snapshot {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMutableSource(source, getSnapshot, subscribe);
}

export function useCacheRefresh(): <T>(?() => T, ?T) => void {
  const dispatcher = resolveDispatcher();
  // $FlowFixMe This is unstable, thus optional
  return dispatcher.useCacheRefresh();
}

我们仔细观察每个hook函数,会发现每个hook函数都调用了 resolveDispatcher()函数,这个函数返回的是 ReactCurrentDispatcher.current:

// packages/react/src/ReactHooks.js
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  // 删除了 __DEV__ 部分的代码
  return ((dispatcher: any): Dispatcher);
}

那么 ReactCurrentDispatcher 又是什么呢?我们在 packages/react/src/ReactCurrentDispatcher.js 文件中看看它的定义:

// packages/react/src/ReactCurrentDispatcher.js
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';

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

export default ReactCurrentDispatcher;

可以看到,ReactCurrentDispatcher 仅仅是一个只有 current 属性的对象。

通过 ReactCurrentDispatcher.current 关键词全局查找,最终在 packages/react-reconciler/src/ReactFiberHooks.new.js 文件中找到了对 ReactCurrentDispatcher.current 的赋值。

// packages/react-reconciler/src/ReactFiberHooks.new.js
// 代码有删减,仅贴出关键性代码
export function renderWithHooks<Props, SecondArg>(
    current: Fiber | null,
    workInProgress: Fiber,
    Component: (p: Props, arg: SecondArg) => any,
    props: Props,
    secondArg: SecondArg,
    nextRenderLanes: Lanes,
  ): any {
  
  
    if (__DEV__) {
      // 删除了 Dev 部分的代码
    } else {
      // 对应的生命周期对象挂载到 ReactCurrentDispatcher.current 上
      ReactCurrentDispatcher.current =
        current === null || current.memoizedState === null
          // 组件挂载时 hook 的初始化
          ? HooksDispatcherOnMount
          // 组件更新时 hook 的初始化
          : HooksDispatcherOnUpdate;
    }
  
  
  
    let children = Component(props, secondArg);
    // 检查在渲染阶段是否更新
    if (didScheduleRenderPhaseUpdateDuringThisPass) {
      do {
  
        ReactCurrentDispatcher.current = __DEV__
          // 开发环境
          ? HooksDispatcherOnRerenderInDEV
          // 生产环境
          : HooksDispatcherOnRerender;
  
        children = Component(props, secondArg);
      } while (didScheduleRenderPhaseUpdateDuringThisPass);
    }
    // 错误捕获处理
    ReactCurrentDispatcher.current = ContextOnlyDispatcher;
  
  
    return children;
  }
  • 在组件挂载时,将 ReactCurrentDispatcher.current 赋值为 HooksDispatcherOnMount 对象。

  • 在组件更新时,将 ReactCurrentDispatcher.current 赋值为 HooksDispatcherOnUpdate。

  • 在render阶段,将 ReactCurrentDispatcher.current 赋值为 HooksDispatcherOnRerender 对象。

我们来看看这三个对象:

// packages/react-reconciler/src/ReactFiberHooks.new.js

// 组件挂载阶段赋值给 ReactCurrentDispatcher.current  HooksDispatcherOnMount 对象
const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useOpaqueIdentifier: mountOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};


// 组件更新阶段赋值给 ReactCurrentDispatcher.current  HooksDispatcherOnUpdate 对象
const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useOpaqueIdentifier: updateOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};


// 组件render阶段赋值给 ReactCurrentDispatcher.current  HooksDispatcherOnRerender 对象
const HooksDispatcherOnRerender: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: rerenderReducer,
  useRef: updateRef,
  useState: rerenderState,
  useDebugValue: updateDebugValue,
  useDeferredValue: rerenderDeferredValue,
  useTransition: rerenderTransition,
  useMutableSource: updateMutableSource,
  useOpaqueIdentifier: rerenderOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};

可以看到,HooksDispatcherOnMount、HooksDispatcherOnUpdate、HooksDispatcherOnRerender 这三个对象分别挂载了组件在 Mount 阶段、Update 阶段、Render 阶段对应的 Hook 处理函数。因此,ReactCurrentDispatcher.current 上挂载的是组件在相应阶段的 Hook 处理函数,这些处理函数是在 renderWithHooks 函数中挂载到 ReactCurrentDispatcher.current 上。

2、入口函数 renderWithHooks

React Fiber 会从 packages/react-reconciler/src/ReactFiberBeginWork.new.js 文件中的 beginWork() 开始执行,对于 Function Component,调用了 updateFunctionComponent() 加载或更新组件:

// packages/react-reconciler/src/ReactFiberBeginWork.new.js

// 函数组件
case FunctionComponent: {
  const Component = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  // 解析组件的 props
  const resolvedProps =
    workInProgress.elementType === Component
      ? unresolvedProps
      : resolveDefaultProps(Component, unresolvedProps);
  // 更新函数组件
  return updateFunctionComponent(
    current,
    workInProgress,
    Component,
    resolvedProps,
    renderLanes,
  );
}

我们再来看看 updateFunctionComponent 函数:

// packages/react-reconciler/src/ReactFiberBeginWork.new.js

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {

  // 删除了Dev部分的代码

  let context;
  // 删除了部分代码

  let nextChildren;
  // 删除了部分代码

  if (__DEV__) {
    ReactCurrentOwner.current = workInProgress;
    setIsRendering(true);
    // 调用 renderWithHooks 获取函数组件的 children
    nextChildren = renderWithHooks(
      current,
      workInProgress,
      Component,
      nextProps,
      context,
      renderLanes,
    );
    if (
      debugRenderPhaseSideEffectsForStrictMode &&
      workInProgress.mode & StrictLegacyMode
    ) {
      disableLogs();
      try {
        // 调用 renderWithHooks 获取函数组件的 children
        nextChildren = renderWithHooks(
          current,
          workInProgress,
          Component,
          nextProps,
          context,
          renderLanes,
        );
      } finally {
        reenableLogs();
      }
    }
    setIsRendering(false);
  } else {
    // 调用 renderWithHooks 获取函数组件的 children
    nextChildren = renderWithHooks(
      current,
      workInProgress,
      Component,
      nextProps,
      context,
      renderLanes,
    );
  }
  // 删除了部分代码

  return workInProgress.child;
}

可以看到,在 updateFunctionComponent 中,调用 renderWithHooks 获取函数组件的 children,然后赋值给 nextChildren,最后在 reconcileChildren() 中将 nextChildren 挂载到了 workInProgress 的 child 属性上。这里我们先不关心 reconcileChildren 是如何处理节点的,我们回到 renderWithHooks 的执行流程。

至此,我们知道了 React Hooks 的渲染核心入口是 renderWithHooks,其被调用的流程可总结如下:

现在,我们重点来看看 renderWithHooks 具体做了什么。

// packages/react-reconciler/src/ReactFiberHooks.new.js

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  renderLanes = nextRenderLanes;
  // 将当前的workInProgressFiber节点存在全局变量currentlyRenderingFiber中,这样方便后面获取Fiber信息
  currentlyRenderingFiber = workInProgress;

  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  // The following should have already been reset

  // 如果current的值为空,说明还没有hook对象被挂载
  // 而根据hook对象结构可知,current.memoizedState指向下一个current
    // 给 ReactCurrentDispatcher.current 赋值
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
 				// 初始化时
        ? HooksDispatcherOnMount
  			// 更新时
        : HooksDispatcherOnUpdate;


  let children = Component(props, secondArg);

  
  // Check if there was a render phase update
  // 检查在渲染阶段是否更新
  
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    // Keep rendering in a loop for as long as render phase updates continue to
    // be scheduled. Use a counter to prevent infinite loops.
    let numberOfReRenders: number = 0;
    do {
      didScheduleRenderPhaseUpdateDuringThisPass = false;
      
      numberOfReRenders += 1;
  

      // Start over from the beginning of the list
      currentHook = null;
      workInProgressHook = null;

      workInProgress.updateQueue = null;


      ReactCurrentDispatcher.current = __DEV__
        ? HooksDispatcherOnRerenderInDEV
        : HooksDispatcherOnRerender;

      children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);
  }

  // 重置
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;


  // 相关属性的初始化
  
  const didRenderTooFewHooks =
    currentHook !== null && currentHook.next !== null;

  renderLanes = NoLanes;
  currentlyRenderingFiber = (null: any);

  currentHook = null;
  workInProgressHook = null;

  didScheduleRenderPhaseUpdate = false;



  if (enableLazyContextPropagation) {
    if (current !== null) {
      if (!checkIfWorkInProgressReceivedUpdate()) {
        
        const currentDependencies = current.dependencies;
        if (
          currentDependencies !== null &&
          checkIfContextChanged(currentDependencies)
        ) {
          markWorkInProgressReceivedUpdate();
        }
      }
    }
  }

  return children;
}

renderWithHooks 为Function Component 的入口函数,它主要包括三部分,首先是给 ReactCurrentDispatcher.current 赋值,然后是didScheduleRenderPhaseUpdateDuringThisPass (检查在渲染阶段是否有更新) 以及一些属性的初始化工作。

我们重点来看 ReactCurrentDispatcher.current 的赋值部分:

// 如果current的值为空,说明还没有hook对象被挂载
// 而根据hook对象结构可知,current.memoizedState指向下一个current
// 给 ReactCurrentDispatcher.current 赋值
ReactCurrentDispatcher.current =
  current === null || current.memoizedState === null
    // 初始化时
    ? HooksDispatcherOnMount
    // 更新时
    : HooksDispatcherOnUpdate;

注意:下面,我们以 useMemo 为例讲解 hook 的挂载。

如果 current (即当前的Fiber) 为空,说明还没有 hook 对象被挂载,说明是首次加载,将 ReactCurrentDispatcher.current.useMemo 赋值成 HooksDispatcherOnMount.useMemo:

// packages/react-reconciler/src/ReactFiberHooks.new.js

const HooksDispatcherOnMount: Dispatcher = {
  // ...
  useMemo: mountMemo,
  // ...
};

如果current (即当前的Fiber) 不为空,说明是在组件更新阶段,ReactCurrentDispatcher.current.useMemo 赋值成 HooksDispatcherOnUpdate.useMemo:

// packages/react-reconciler/src/ReactFiberHooks.new.js

const HooksDispatcherOnUpdate: Dispatcher = {
  // ...
  useMemo: updateMemo,
  // ...
};

因此,我们可以得到这样的等式:

  • 首次加载时:

    useMemo = ReactCurrentDispatcher.current.useMemo = HooksDispatcherOnMount.useMemo = mountMemo;

  • 更新时:

    useMemo = ReactCurrentDispatcher.current.useMemo = HooksDispatcherOnUpdate.useMemo = updateMemo;

为了更直观地看出 useMemo 在首次加载和更新时的处理逻辑,我们将上面的等式转换成流程图,如下:

由上图,我们可以直观地看出,组件在首次加载时,执行 useMemo,其实执行的是 mountMemo,而组件更新时,则执行的是updateMemo 。

其它 hook 同理,不再赘述。

3、总结

在文中,我们介绍了 Hooks 的入口以及入口函数 renderWithHooks。

在 packages/react-reconciler/src/ReactFiberHooks.new.js 文件中,有所有 React Hooks 的核心实现。并在 renderWithHooks 函数中把 Hooks 在 Mount 阶段、Update 阶段、Render 阶段对应的 Hook 处理函数挂载到了 ReactCurrentDispatcher.current 属性上。

当我们调用某个 hook 时,实际上调用的是挂载在 ReactCurrentDispatcher.current 属性上的对应的 hook 处理函数。