likes
comments
collection
share

react hooks核心:hooktype和ReactCurrentDispatcher

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

这里涉及整个hooks的一个入口函数:renderWithHooks这个函数中只截取跟hookstype有关的部分:

{
    if (current !== null && current.memoizedState !== null) {
      // 如果是更新中,那么用HooksDispatcherOnUpdateInDEV
      ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
    } else if (hookTypesDev !== null) {
      // This dispatcher handles an edge case where a component is updating,
      // but no stateful hooks have been used.
      // We want to match the production code behavior (which will use HooksDispatcherOnMount),
      // but with the extra DEV validation to ensure hooks ordering hasn't changed.
      // This dispatcher does that.
      
      // 如果是hookTypesDev不为空,那么用HooksDispatcherOnMountWithHookTypesInDEV
      ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
    } else {
      // 默认情况:用HooksDispatcherOnMountInDEV,这也是一般的初始化的时候用的。
      ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
    }
  }

接下来有7个对象:HooksDispatcherOnMountInDEVHooksDispatcherOnMountWithHookTypesInDEVHooksDispatcherOnUpdateInDEVHooksDispatcherOnRerenderInDEVInvalidNestedHooksDispatcherOnMountInDEVInvalidNestedHooksDispatcherOnUpdateInDEVInvalidNestedHooksDispatcherOnRerenderInDEV这7个对象里都实现了一整套的useMemo、useCallback等,一整套的hooks。但是结构都很相似,我们用useState来对比每个的不同。

先看HooksDispatcherOnMountInDEV

useState: function (initialState) {
    // 改变当前的hookname为useState
      currentHookNameInDev = 'useState';
      // 把hookname放进一个数组hookTypesDev里
      mountHookTypesDev();
      // 下面两行是迭代dispatch的。等于是把当前的存到prev里,然后让当前变成InvalidNestedHooksDispatcherOnMountInDEV
      var prevDispatcher = ReactCurrentDispatcher$1.current;
      ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;

      try {
        return mountState(initialState);
      } finally {
        // 必定执行,将prev放到当前,因为不能再使用InvalidNestedHooksDispatcherOnMountInDEV了。
        // 后面会说为什么不能再使用InvalidNestedHooksDispatcherOnMountInDEV
        ReactCurrentDispatcher$1.current = prevDispatcher;
      }
    },

附:mountHookTypesDev方法,很简单,就是把当前的hookname放进一个数组hookTypesDev里。

function mountHookTypesDev() {
  {
    var hookName = currentHookNameInDev;

    if (hookTypesDev === null) {
      hookTypesDev = [hookName];
    } else {
      hookTypesDev.push(hookName);
    }
  }
}

再来看HooksDispatcherOnMountWithHookTypesInDEV和HooksDispatcherOnMountInDEV只有一句的区别

useState: function (initialState) {
      currentHookNameInDev = 'useState';
      // 和HooksDispatcherOnMountInDEV只有这一行不同,这里的更新。其他完全一样。
      updateHookTypesDev();
      var prevDispatcher = ReactCurrentDispatcher$1.current;
      // 这里和HooksDispatcherOnMountInDEV一样。
      ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;

      try {
        return mountState(initialState);
      } finally {
        ReactCurrentDispatcher$1.current = prevDispatcher;
      }
    },

再看HooksDispatcherOnUpdateInDEV和HooksDispatcherOnMountWithHookTypesInDEV只有一句的区别

useState: function (initialState) {
      currentHookNameInDev = 'useState';
      updateHookTypesDev();
      var prevDispatcher = ReactCurrentDispatcher$1.current;
      // 这里也是有区别的。但不太重要
      ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;

      try {
        // 和HooksDispatcherOnMountWithHookTypesInDEV只有这一句的区别
        return updateState(initialState);
      } finally {
        ReactCurrentDispatcher$1.current = prevDispatcher;
      }
    },

再看HooksDispatcherOnRerenderInDEV这里需要说明,HooksDispatcherOnRerenderInDEV在renderWithHooks方法中是在后面赋值的,可以理解为是render是执行的。和HooksDispatcherOnUpdateInDEV比,又只有一句的区别

useState: function (initialState) {
      currentHookNameInDev = 'useState';
      updateHookTypesDev();
      var prevDispatcher = ReactCurrentDispatcher$1.current;
      // 这里也是有区别的。但不太重要
      ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV;

      try {
        // 只有这一句的区别  
        return rerenderState(initialState);
      } finally {
        ReactCurrentDispatcher$1.current = prevDispatcher;
      }
    },

InvalidNested的mount、update、render这三个一块说。InvalidNestedHooksDispatcherOnMountInDEV

useState: function (initialState) {
      currentHookNameInDev = 'useState';
      // 都增加了这行
      warnInvalidHookAccess();
      mountHookTypesDev();
      var prevDispatcher = ReactCurrentDispatcher$1.current;
      // 这行略有不同
      ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;

      try {
        return mountState(initialState);
      } finally {
        ReactCurrentDispatcher$1.current = prevDispatcher;
      }
    },

InvalidNestedHooksDispatcherOnUpdateInDEV:

useState: function (initialState) {
      currentHookNameInDev = 'useState';
      // 都增加了这行
      warnInvalidHookAccess();
      updateHookTypesDev();
      var prevDispatcher = ReactCurrentDispatcher$1.current;
      // 这行略有不同,注意这里是update,不是render
      ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;

      try {
        return rerenderState(initialState);
      } finally {
        ReactCurrentDispatcher$1.current = prevDispatcher;
      }
    },

这三部分,都是为了warnInvalidHookAccess这个警告代码:不要在useEffect、useMemo里创建hooks,你只能在顶层的react组件中创建hooks。源码如下:

var warnInvalidHookAccess = function () {
    error('Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' + 'You can only call Hooks at the top level of your React function. ' + 'For more information, see ' + 'https://reactjs.org/link/rules-of-hooks');
  };

总结

可以理解,每一个都是,如果在mountState、updateState、renderState区间,又做了mount、update、render的话,就会报警告,同时下一次还是在InvalidNested。这个功能在state里,一个比较典型的场景是当setState方法里又执行了hooks方法,就会报error。或者在useMemo、useEffect方法里创建hooks,也会报error。