开始

React.js中可以看到Hooks导入的代码:

import {
  useCallback,
  useContext,
  useEffect,
  useImperativeMethods,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from './ReactHooks';

那么我们就来看看具体的代码是怎样的:

function resolveDispatcher() {
  const dispatcher = ReactCurrentOwner.currentDispatcher;
  return dispatcher;
}

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

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

export function useContext<T>(
  Context: ReactContext<T>,
  observedBits: number | boolean | void,
) {
  const dispatcher = resolveDispatcher();
  // dev code
  return dispatcher.useContext(Context, observedBits);
}

以上就是三个最常用的HooksReact中的源码,可见他们也跟其他React的API一样,只管定义,不管实现。他们都调用了ReactCurrentOwner.currentDispatcher.xxx对应的方法。那么这个ReactCurrentOwner.currentDispatcher是啥呢?

在我们执行renderRoot开始渲染的时候,我们会设置这个值:

import {Dispatcher} from './ReactFiberDispatcher';

if (enableHooks) {
  ReactCurrentOwner.currentDispatcher = Dispatcher;
}

并且在离开renderRoot的时候设置为null。那么这个Dispatcher又是啥呢?

// ReactFiberDispatcher.js
import {readContext} from './ReactFiberNewContext';
import {
  useCallback,
  useContext,
  useEffect,
  useImperativeMethods,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from './ReactFiberHooks';

export const Dispatcher = {
  readContext,
  useCallback,
  useContext,
  useEffect,
  useImperativeMethods,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
};

OK,Hooks方法的源头代码找到了。需要注意的是Hooks只有FunctionalComponent被更新的时候才会被调用,所以我们肯定需要关心一下FunctionalComponent的更新过程。

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  prepareToUseHooks(current, workInProgress, renderExpirationTime);
  nextChildren = Component(nextProps, context);
  nextChildren = finishHooks(Component, nextProps, nextChildren, context);

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

可以看到这里增加了三个方法调用:prepareToReadContextprepareToUseHooksfinishHooks。后两个都明显跟Hooks有关,而第一个是读取新的Context API的,因为在Hooks中有读取Context的操作,所以增加这个就无可厚非了。我们看一下prepareToUseHooksfinishHooks分别做了什么。

prepareToUseHooks

这个方法来自ReactFiberHooks.js,明显只是初始化一些模块内的全局变量。

export function prepareToUseHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  nextRenderExpirationTime: ExpirationTime,
): void {
  renderExpirationTime = nextRenderExpirationTime;
  currentlyRenderingFiber = workInProgress;
  firstCurrentHook = current !== null ? current.memoizedState : null;
}

finishHooks

相比之下finishHooks则复杂很多,一进来就有一个while循环,这个循环是因为Hooks提供了我们一个功能:如果在一次更新中(也就是调用FunctionalComponent的过程中)如果直接调用了类似setState的Hooks API产生了新的更新,则会在当前的渲染周期中直接执行更新。这个大家可以先了解一下,后面我们具体讲Hooks的实现会再具体讲到。

后面设置了renderedWork.updateQueue,就非常类似于HostComponentClassComponent了,本来FunctionalComponentcommit阶段是完全没有更新的,但是现在Hooks给了他产生side effect的能力,所以这就是记录这些side effectqueue

后面就是初始化全局变量了,就先不多讲了。

export function finishHooks(
  Component: any,
  props: any,
  children: any,
  refOrContext: any,
): any {
  while (didScheduleRenderPhaseUpdate) {
    didScheduleRenderPhaseUpdate = false;
    numberOfReRenders += 1;

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

    children = Component(props, refOrContext);
  }
  renderPhaseUpdates = null;
  numberOfReRenders = 0;

  const renderedWork: Fiber = (currentlyRenderingFiber: any);

  renderedWork.memoizedState = firstWorkInProgressHook;
  renderedWork.expirationTime = remainingExpirationTime;
  renderedWork.updateQueue = (componentUpdateQueue: any);

  const didRenderTooFewHooks =
    currentHook !== null && currentHook.next !== null;

  renderExpirationTime = NoWork;
  currentlyRenderingFiber = null;

  firstCurrentHook = null;
  currentHook = null;
  firstWorkInProgressHook = null;
  workInProgressHook = null;

  remainingExpirationTime = NoWork;
  componentUpdateQueue = null;

  return children;
}

接下去我们就来看具体的Hooks API的实现。

results matching ""

    No results matching ""

    Jokcy的二维码

    扫码添加Jokcy,更多更新更优质的前端学习内容不断更新中,期待与你一起成长!

    Jokcy的二维码