zoukankan      html  css  js  c++  java
  • react 源码之setState

    今天看了react源码,仅以记录。

    1:monorepo (react 的代码管理方式)

    与multirepo 相对。 monorepo是单代码仓库, 是把所有相关项目都集中在一个代码仓库中,每个module独立发布,每个module都有自己的依赖项(package.json),能够作为独立的npm package发布,只是源码放在一起维护。

    下图是典型monorepo目录图,react为例

    2: setState

    ✋ setState  "react/src/ReactBaseClasses.js"

     * @param {object|function} partialState Next partial state or function to
     *        produce next partial state to be merged with current state.
     * @param {?function} callback Called after state is updated.
     * @final
     * @protected
     */
    Component.prototype.setState = function(partialState, callback) {
      invariant(
        typeof partialState === 'object' ||
          typeof partialState === 'function' ||
          partialState == null,
        'setState(...): takes an object of state variables to update or a ' +
          'function which returns an object of state variables.',
      );
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };

    可以看到,干了两件事情:第一:调用方法invariant(), 第二:调用 this.updater.enqueueSetState

    ✋ invariant() "shared/invariant"  让我们先去看看第一个方法干嘛了!

    export default function invariant(condition, format, a, b, c, d, e, f) {
      validateFormat(format);
    
      if (!condition) { // 导出了一个方法,可以看出如果condition为false的话, 抛出错误
        let error;
        if (format === undefined) {
          error = new Error(
            'Minified exception occurred; use the non-minified dev environment ' +
              'for the full error message and additional helpful warnings.',
          );
        } else {
          const args = [a, b, c, d, e, f];
          let argIndex = 0;
          error = new Error(
            format.replace(/%s/g, function() {
              return args[argIndex++];
            }),
          );
          error.name = 'Invariant Violation';
        }
    
        error.framesToPop = 1; // we don't care about invariant's own frame
        throw error;
      }
    }

    得出,这个方法就是判断partialState 的type, 不正确的话抛错误。 Facebook工程师把这个抛错误封装了成了invariant函数,嗯,以后工作中可以这样做!

    ✋ this.updater.enqueueSetState  让我们再去看看第二个是干嘛了!

    首先,this.updater 什么鬼:

    import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
    /**
     * Base class helpers for the updating state of a component.
     */
    function Component(props, context, updater) {//在这个文件中导出了两个构造函数Component和PureComponent
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject;
      // We initialize the default updater but the real one gets injected by the
      // renderer.   
      this.updater = updater || ReactNoopUpdateQueue; // 这里是this.updater, 这个标黄的 是default updater
    }

    ✋ ReactNoopUpdateQueue  "react/src/ReactNoopUpdateQueue.js" 导出的一个对象,里面有好几个方法,其中一个就是setState用到的this.updater.enqueueSetState(this, partialState, callback, 'setState'):

    enqueueSetState: function(
        publicInstance, // 实例this
        partialState, // 新的state
        callback, // 回调   
        callerName,
      ) {
        warnNoop(publicInstance, 'setState');
      },
    // 啊 这个里面是default updater
    
    

    ✋  enqueueSetState定义 "react-dom/src/server/ReactPartialRenderer.js"    实际的updater从哪里来的呢?哎,我是暂时没找到,但是,我知道这个实际的updater肯定有enqueueSetState方法,那我就全局搜索一下,找到enqueueSetState的定义:

       let updater = {  //这里是updater对象,里面各种方法
          isMounted: function(publicInstance) {
            return false;
          },
          enqueueForceUpdate: function(publicInstance) {
            if (queue === null) {
              warnNoop(publicInstance, 'forceUpdate');
              return null;
            }
          },
          enqueueReplaceState: function(publicInstance, completeState) {
            replace = true;
            queue = [completeState];
          },
          enqueueSetState: function(publicInstance, currentPartialState) {
            if (queue === null) { // 这是一个错误情况,下面代码中warnNoop()方法就是在dev环境中给出空操作警告的
              warnNoop(publicInstance, 'setState');
              return null;
            }
            queue.push(currentPartialState); // 把现在要更新的state  push到了一个queue中
          },
        };
         // 接下来代码解决了我上面找不到实际updater的疑问!
        let inst;
        if (shouldConstruct(Component)) {
          inst = new Component(element.props, publicContext, updater); // 在new的时候,就把上面真实的updater传进去啦!!!
    .......后面还有好多,不过与我这一期无关了.

    ✋  queue 这是react提升性能的关键。并不是每次调用setState react都立马去更新了,而是每次调用setState, react只是push到了待更新的queue中! 下面是对这个queue的处理!

          if (queue.length) {
            let oldQueue = queue;
            let oldReplace = replace;
            queue = null;
            replace = false;
    
            if (oldReplace && oldQueue.length === 1) { // 队列里面,只有一个,直接更换。
              inst.state = oldQueue[0];
            } else {  // 队列里面有好几个,先进行合并,再更新
              let nextState = oldReplace ? oldQueue[0] : inst.state;
              let dontMutate = true;
              for (let i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {
                let partial = oldQueue[i];
                let partialState =
                  typeof partial === 'function'
                    ? partial.call(inst, nextState, element.props, publicContext)
                    : partial;
                if (partialState != null) {  // 这里合并啦,新的是nextState
                  if (dontMutate) {
                    dontMutate = false;
                    nextState = Object.assign({}, nextState, partialState);
                  } else {
                    Object.assign(nextState, partialState);
                  }
                }
              }   
              inst.state = nextState;  //最后赋值给实例
            }
          } else {
            queue = null;
          }
  • 相关阅读:
    CSS text-indent 属性
    从零开始搭建一个简单的基于webpack的vue开发环境
    mui is not defined
    vue项目中引入mui.poppicker.js文件时报错“Uncaught ReferenceError: mui is not defined”
    vue-cli创建的项目中引入第三方库报错'caller', 'calle', and 'arguments' properties may not be...
    微信小程序点击顶部导航栏切换样式
    js学习笔记-日期对象
    setTimeout()和setInterval()的用法及区别
    Ubuntu 14.04 下安装redis后运行redis-cli 报出redis Connection refused错误【已解决】
    Ubuntu start:未知任务:mysql
  • 原文地址:https://www.cnblogs.com/yadiblogs/p/10446748.html
Copyright © 2011-2022 走看看