likes
comments
collection
share

react hooks 本质探索 - useMemo、useEffect源码解析

作者站长头像
站长
· 阅读数 30

useRef和useCallback感觉是最容易理解的两个原生use了:react hooks本质探索 - useRef源码详解react hooks 本质探索 - useCallback源码解析

因为之后要说的,都涉及ReactCurrentDispatcher$1这个对象。这个对象到底是什么意思?详见这里:(编辑中)

我们直接看useMemo和useEffect的源码:

useMemo: function (create, deps) {
  currentHookNameInDev = 'useMemo';
  // 这里详见ReactCurrentDispatcher
  mountHookTypesDev();
  // 很简单的函数:用于确认deps是不是数组,不是的话抛出错误
  checkDepsAreArrayDev(deps);
  
  // 更新dispatch,详见ReactCurrentDispatcher
  // 这里这么写,是怕再create方法里有用到use类的方法,这样就会执行InvalidNested里的hook方法。
  var prevDispatcher = ReactCurrentDispatcher$1.current;
  ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;

  try {
    return mountMemo(create, deps);
  } finally {
    ReactCurrentDispatcher$1.current = prevDispatcher;
  }
},
useEffect: function (create, deps) {
  currentHookNameInDev = 'useEffect';
  // 这里详见ReactCurrentDispatcher
  mountHookTypesDev();
  // 很简单的函数:用于确认deps是不是数组,不是的话抛出错误
  checkDepsAreArrayDev(deps);
  return mountEffect(create, deps);
},

两者的区别只有 ReactCurrentDispatcher$1部分。为什么会有这个区分?因为useMemo是在渲染中的时候执行,而useEffect是在渲染后执行。

依据这个区分,我们可以合理猜测:只要有ReactCurrentDispatcher$1部分的use,都是在渲染中执行。只有这几个use是在渲染中执行:useMemo、useReducer、useState。

接下来看useMemo在mount和update时的源码(源码中,这两个函数就是连在一起的):

function mountMemo(nextCreate, deps) {
  var hook = mountWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  // nextCreate就是我们在创建useMemo时传入的函数。
  var nextValue = nextCreate();
  // 存入hook里,等待update时使用
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateMemo(nextCreate, deps) {
  var hook = updateWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  var prevState = hook.memoizedState;

  if (prevState !== null) {
    // Assume these are defined. If they're not, areHookInputsEqual will warn.
    if (nextDeps !== null) {
      var prevDeps = prevState[1];

      // 判断跟之前的deps值是否一样,如果一样,直接返回前值
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }

  // 这里开始,是判断deps值不一样的情况
  // 包含了前值或者当前值为null的情况,也认为是不一样。
  var nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

updateMemo函数的结构,跟useCallback的update很像:react hooks 本质探索 - useCallback源码解析

接下来看useEffect的mount和update:

function mountEffect(create, deps) {
  {
    // 这部分只跟jest相关,好像还和fiber相关,总之没关系。  
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if ('undefined' !== typeof jest) {
      warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber$1);
    }
  }

  return mountEffectImpl(Update | Passive, Passive$1, create, deps);
}

function updateEffect(create, deps) {
  {
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if ('undefined' !== typeof jest) {
      warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber$1);
    }
  }

  return updateEffectImpl(Update | Passive, Passive$1, create, deps);
}

关键是mountEffectImpl和updateEffectImpl:

function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
  var hook = mountWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber$1.flags |= fiberFlags;
  // pushEffect应该是把当前effect放到一个渲染队列中
  hook.memoizedState = pushEffect(HasEffect | hookFlags, create, undefined, nextDeps);
}

function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
  var hook = updateWorkInProgressHook();
  var nextDeps = deps === undefined ? null : deps;
  var destroy = undefined;

  if (currentHook !== null) {
    var prevEffect = currentHook.memoizedState;
    destroy = prevEffect.destroy;

    if (nextDeps !== null) {
      var prevDeps = prevEffect.deps;

      if (areHookInputsEqual(nextDeps, prevDeps)) {
        pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber$1.flags |= fiberFlags;
  hook.memoizedState = pushEffect(HasEffect | hookFlags, create, destroy, nextDeps);
}

这部分先不做详细解释,因为牵涉的东西比较多。简单理解,就是push到了一个渲染队列里,在周期之外进行渲染