美文网首页ReactreactWeb前端之路
React基础2--深入挖掘setState

React基础2--深入挖掘setState

作者: Dabao123 | 来源:发表于2017-08-31 23:40 被阅读191次
    React基础2--深入挖掘setState

    众所周知,react通过this.state来访问,通过setState()方法来更新state。当this.setState()被调用的时候react会调用render重新渲染。
    当执行setState的时候,会将需要更新的state合并到状态队列中去,这样可以高效的批量更新state。假如直接更改this.state的值那么state将不会被放到状态队列中去,当合并state队列的时候则忽略之前被修改的state,造成无法预知的错误。
    首先,我们看一段setState源码

    // 将setState事务放入队列中
    ReactComponent.prototype.setState = function (partialState, callback) {
      !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
      this.updater.enqueueSetState(this, partialState);
      if (callback) {
        this.updater.enqueueCallback(this, callback, 'setState');
      }
    };
    

    // 合并队列

    enqueueSetState: function (publicInstance, partialState) {
        // 获取ReactComponent组件对象
        // publicInstance 指向 ReactComponent
        var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
    
        if (!internalInstance) {
          return;
        }
        // 合并队列
        // 如果_pendingStateQueue为空,则创建它。可以发现队列是数组形式实现的
        var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
        // 此处partialState 既是你传入的参数
        queue.push(partialState);
        // 将要更新的ReactComponent放入数组中
        enqueueUpdate(internalInstance);
      }
    

    getInternalInstanceReadyForUpdate()

    function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
      var internalInstance = ReactInstanceMap.get(publicInstance);
      // 从map取出ReactComponent组件,
      //mountComponent时把ReactElement作为key,将ReactComponent存入了map中,
      //ReactComponent是React组件的核心,包含各种状态,数据和操作方法。而ReactElement则仅仅是一个数据类。
      if (!internalInstance) {
        if (process.env.NODE_ENV !== 'production') {
          var ctor = publicInstance.constructor;
        }
        return null;
      }
      return internalInstance;
    }
    

    enqueueUpdate()

    function enqueueUpdate(component) {
      ensureInjected();
    
      // 如果不是正处于创建或更新组件阶段,则处理update事务
      if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
      }
    
      // 如果正在创建或更新组件,则暂且先不处理update,只是将组件放在dirtyComponents数组中
      dirtyComponents.push(component);
    }
    
    batchedUpdates: function (callback, a, b, c, d, e) {
      var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
      // 批处理最开始时,将isBatchingUpdates设为true,表明正在更新
      ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    
      // The code is written this way to avoid extra allocations
      if (alreadyBatchingUpdates) {
        callback(a, b, c, d, e);
      } else {
        // 以事务的方式处理updates,后面详细分析transaction
        transaction.perform(callback, null, a, b, c, d, e);
      }
    }
    
    var RESET_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: function () {
        // 事务批更新处理结束时,将isBatchingUpdates设为了false
        ReactDefaultBatchingStrategy.isBatchingUpdates = false;
      }
    };
    var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
    

    源码分析(我们看看从调用setState到组建更新发生了什么):
    当我们调用setState方法时,实际上是执行enqueueSetState方法。
    enqueueSetState方法接受两个参数,一个this指向ReactComponent组件对象,一个是我们设置的参数。
    enqueueSetState方法接受参数后,会调用getInternalInstanceReadyForUpdate方法,该方法接受ReactComponent组件对象,然后从map中取出ReactComponent组件并返回。
    如果返回不为空,则合并队列,如果_pendingStateQueue为空,则创建它,然后将我们设置的参数合并到一起。
    最后调用enqueueUpdate方法。将更新的ReactComponent组件作为参数。
    enqueueUpdate方法会先判断正否处于创建或更新组件阶段,如果是则暂且先不处理update,只是将组件放在dirtyComponents数组中。否则,将调用batchedUpdates方法。
    enqueueUpdate包含了React避免重复render的逻辑。mountComponent和updateComponent方法在执行的最开始,会调用到batchedUpdates进行批处理更新,此时会将isBatchingUpdates设置为true,也就是将状态标记为现在正处于更新阶段了。之后React以事务的方式处理组件update,事务处理完后会调用wrapper.close(), 而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES这个wrapper,故最终会调用RESET_BATCHED_UPDATES.close(), 它最终会将isBatchingUpdates设置为false。
    到此,我相信认真读的童鞋,肯定有个疑问。。。
    那就是transaction.perform(callback, null, a, b, c, d, e);里面的callback就是enqueueUpdate;
    那么这样不就进入到死循环了么,怎么还调用updateComponent更新组件呢?带着疑问往下看。。

    perform: function (method, scope, a, b, c, d, e, f) {
        var errorThrown;
        var ret;
        try {
          this._isInTransaction = true;
          errorThrown = true;
          // 先运行所有wrapper中的initialize方法
          this.initializeAll(0);
    
          // 再执行perform方法传入的callback
          ret = method.call(scope, a, b, c, d, e, f);
          errorThrown = false;
        } finally {
          try {
            if (errorThrown) {
              // 最后运行wrapper中的close方法
              try {
                this.closeAll(0);
              } catch (err) {}
            } else {
              // 最后运行wrapper中的close方法
              this.closeAll(0);
            }
          } finally {
            this._isInTransaction = false;
          }
        }
        return ret;
      },
    
      initializeAll: function (startIndex) {
        var transactionWrappers = this.transactionWrappers;
        // 遍历所有注册的wrapper
        for (var i = startIndex; i < transactionWrappers.length; i++) {
          var wrapper = transactionWrappers[i];
          try {
            this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
            // 调用wrapper的initialize方法
            this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
          } finally {
            if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
              try {
                this.initializeAll(i + 1);
              } catch (err) {}
            }
          }
        }
      },
    
      closeAll: function (startIndex) {
        var transactionWrappers = this.transactionWrappers;
        // 遍历所有wrapper
        for (var i = startIndex; i < transactionWrappers.length; i++) {
          var wrapper = transactionWrappers[i];
          var initData = this.wrapperInitData[i];
          var errorThrown;
          try {
            errorThrown = true;
            if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) {
              // 调用wrapper的close方法,如果有的话
              wrapper.close.call(this, initData);
            }
            errorThrown = false;
          } finally {
            if (errorThrown) {
              try {
                this.closeAll(i + 1);
              } catch (e) {}
            }
          }
        }
        this.wrapperInitData.length = 0;
      }
    

    通过transaction.perform的源码我们可以知道,perform先运行wrapper中的initialize方法,再执行perform方法传入的callback, 最后运行wrapper中的close方法。而在最后

    var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
    

    wrapper中注册了两个wrapper:
    FLUSH_BATCHED_UPDATES

    var FLUSH_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
    };
    

    flushBatchedUpdates ()

    var flushBatchedUpdates = function () {
      // 循环遍历处理完所有dirtyComponents
      while (dirtyComponents.length || asapEnqueued) {
        if (dirtyComponents.length) {
          var transaction = ReactUpdatesFlushTransaction.getPooled();
          // close前执行完runBatchedUpdates方法,这是关键
          transaction.perform(runBatchedUpdates, null, transaction);
          ReactUpdatesFlushTransaction.release(transaction);
        }
        if (asapEnqueued) {
          asapEnqueued = false;
          var queue = asapCallbackQueue;
          asapCallbackQueue = CallbackQueue.getPooled();
          queue.notifyAll();
          CallbackQueue.release(queue);
        }
      }
    };
    
    function runBatchedUpdates(transaction) {
      var len = transaction.dirtyComponentsLength;
      dirtyComponents.sort(mountOrderComparator);
    
      for (var i = 0; i < len; i++) {
        // dirtyComponents中取出一个component
        var component = dirtyComponents[i];
    
        // 取出dirtyComponent中的未执行的callback,下面就准备执行它了
        var callbacks = component._pendingCallbacks;
        component._pendingCallbacks = null;
    
        var markerName;
        if (ReactFeatureFlags.logTopLevelRenders) {
          var namedComponent = component;
          if (component._currentElement.props === component._renderedComponent._currentElement) {
            namedComponent = component._renderedComponent;
          }
        }
        // 执行updateComponent
        ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
    
        // 执行dirtyComponent中之前未执行的callback
        if (callbacks) {
          for (var j = 0; j < callbacks.length; j++) {
            transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
          }
        }
      }
    }
    

    performUpdateIfNecessary()

    performUpdateIfNecessary: function (transaction) {
        if (this._pendingElement != null) {
          // receiveComponent会最终调用到updateComponent,从而刷新View
          ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
        }
    
        if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
          // 执行updateComponent,从而刷新View。这个流程在React生命周期中讲解过
          this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
        }
      },
    

    到这一步是不是看到了receiveComponent 和 updateComponent有点欣喜若狂呢。
    flushBatchedUpdates 方法会循环遍历处理完所有dirtyComponents,在close前执行完runBatchedUpdates方法。
    runBatchedUpdates方法调用performUpdateIfNecessary();
    最后进入update阶段。

    setState循环调用风险

    当调用setState时,实际上会执行enqueueSetState方法,对_pendingStateQueue合并,然后执行enqueueUpdate更新state。假如在componentWillUpdate调用setState此时,this._pendingStateQueue != null 所以,performUpdateIfNecessary方法会调用updateComponent方法更新组件,一次造成循环调用。

    setState封装到异步函数里为什么就变成了同步的?

    我们理一下setState的过程:
    首先:enqueueUpdate方法
    在该方法中通过判断batchingStrategy.isBatchingUpdates的值来决定如何处理。
    我们在此处打印一下batchingStrategy.isBatchingUpdates

    Paste_Image.png Paste_Image.png Paste_Image.png

    将flushBatchedUpdates

    Paste_Image.png
    Paste_Image.png Paste_Image.png

    最后一步我们打印updateComponent里面的state更新后的state

    Paste_Image.png

    假如我们在componentDidMount里面设置setState

     componentDidMount(){
           console.log('父组件didmount')
           this.setState({num:this.state.num+2})
           console.log(this.state.num)
           console.log('ok')
        }
    

    结果为:

    Paste_Image.png

    在enqueueUpdate方法里面batchingStrategy.isBatchingUpdates为true。代表正在穿件组件或更新,此时:
    暂且先不处理update,只是将组件放在dirtyComponents数组中
    dirtyComponents.push(component);
    然后继续执行创建或更新组件。直到执行完didcomponent。
    此时,打印this.state还是之前的,因为还没完成更新。
    继续往下执行,直到updateComponent。在updateComponent里面
    执行

    var nextState = this._processPendingState(nextProps, nextContext);
    

    state正式更新。
    假如:
    我们将setState封装到setTimeout函数里:

    componentDidMount(){
           console.log('父组件didmount')
           setTimeout(()=>{
             console.log('start')
             this.setState({num:this.state.num+2})
             console.log(this.state.num)
           })
           console.log(this.state.num)
           console.log('ok')
        }
    

    执行结果:

    Paste_Image.png

    因为setTimeout是异步的,所以componentDidMount会在继续执行,直到结束。此时
    会执行ReactDefaultBatchingStrategy.isBatchingUpdates = false;
    代表已经创建结束。
    因此,在执行setState的时候batchingStrategy.isBatchingUpdates = false.
    所以将执行

    if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
      }
    

    操作,进而执行

    Paste_Image.png

    并且isBatchingUpdates被设置成 true,将组建设置成更新状态。
    这时候react重新调用enqueueUpdate方法。然后继续执行后续操作。

    将setState放到方法里执行

       sets(){
            this.setState({
                value : true
            })
            console.log(this.setState.value)
        }
        render(){
            return(
                <div>
                    
                    <button onClick={this.sets.bind(this)}>切换</button>
                </div>
            )
        }
    

    执行结果,点击切换后:

    Paste_Image.png

    我们可以看到在最开始,无论是在周期函数,还是点击时间,都会调用
    batchedUpdates方法;

    batchedUpdates: function (callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
        console.log('batchedUpdates')
        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        if (alreadyBatchingUpdates) {
          callback(a, b, c, d, e);
        } else {
          transaction.perform(callback, null, a, b, c, d, e);
        }
      }
    

    设置 ReactDefaultBatchingStrategy.isBatchingUpdates = true;代表组件处于更新状态。
    在结束都会调用

     close: function () {
        console.log('isBatchingUpdates = false')
        ReactDefaultBatchingStrategy.isBatchingUpdates = false;
      }
    

    设置 ReactDefaultBatchingStrategy.isBatchingUpdates = true.代表组件处于更新状态。
    而在设置state的过程中,检测到组件处于更新状态或新建状态,则暂且先不处理update,只是将组件放在dirtyComponents数组中。所以要等到组件更新完成后在进行更新state,所以setState看起来像是异步的。但是将setState放到异步函数里面这时,组件状态已经处于完成更新或新建状态,所以setState不用等了直接就可以处理,所以像是同步的。
    其他文章:
    React基础1--生命周期详解
    React基础3--diff算法

    相关文章

      网友评论

      • Dabao123:下一篇讲一下 virtual dom以及react的 diff算法。

      本文标题:React基础2--深入挖掘setState

      本文链接:https://www.haomeiwen.com/subject/nzoadxtx.html