美文网首页
React学习之(二)setState的更新机制

React学习之(二)setState的更新机制

作者: 殊一ONLY | 来源:发表于2017-11-22 22:14 被阅读0次

    继续上文。。。flushBatchedUpdates 的源码

    var flushBatchedUpdates = function () {
      // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
      // array and perform any updates enqueued by mount-ready handlers (i.e.,
      // componentDidUpdate) but we need to check here too in order to catch
      // updates enqueued by setState callbacks and asap calls.
      while (dirtyComponents.length || asapEnqueued) {
        if (dirtyComponents.length) {
          var transaction = ReactUpdatesFlushTransaction.getPooled();
          transaction.perform(runBatchedUpdates, null, transaction);
          ReactUpdatesFlushTransaction.release(transaction);
        }
        if (asapEnqueued) {
          asapEnqueued = false;
          var queue = asapCallbackQueue;
          asapCallbackQueue = CallbackQueue.getPooled();
          queue.notifyAll();
          CallbackQueue.release(queue);
        }
      }
    };
    

          在此又出现了一个新的事物ReactUpdatesFlushTransaction,它主要用来捕获在运行flushBatchedUpdate后将要运行的updates。这个过程比较复杂,因为componentDidUpdate或则setState后的回调方法需要进入下一个更新队列。另外这个事物是getpooled来的,而不是实时创建的,这样做的好处是避免不必要的垃圾收集。另外这个地方也涉及到asp update的内容,后续将介绍到。
          在上述源码中,需要在ReactUpdatesFlushTransaction事物的输入参数中调用了runBatchedUpdates,我们来看一看这个方法的逻辑功能:

    function runBatchedUpdates(transaction) {
      var len = transaction.dirtyComponentsLength;
    //父组件通常比子组件有更高的reconcile等级,所以需要对dirtyComponents进行排序
      dirtyComponents.sort(mountOrderComparator);
       // reconciling时任何更新队列在整个批量处理后必须执行。然而,假设dirtyComponents是[A,B],B和C是A的child如果C的渲染器为B进行入队列更新,那么在一次批量处理中B需要更新两次。由于B已经更新了,我们将会忽略它,再次更新它的方式只能通过检查bact的数量
      updateBatchNumber++;
      for (var i = 0; i < len; i++) {
        var component = dirtyComponents[i];
       //如果,在任何新的更新队列中发生了performUpdateIfNecessary,那么只能在下一次发生渲染时执行回调所以,先将回调存储下来。(后面来看一看什么是performUpdateIfNecessary)
        var callbacks = component._pendingCallbacks;
        component._pendingCallbacks = null;
        var markerName;
        if (ReactFeatureFlags.logTopLevelRenders) {
          var namedComponent = component;
          // Duck type TopLevelWrapper. This is probably always true.
          if (component._currentElement.type.isReactTopLevelWrapper) {
            namedComponent = component._renderedComponent;
          }
          markerName = 'React update: ' + namedComponent.getName();
          console.time(markerName);
        }
        ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
        if (markerName) {
          console.timeEnd(markerName);
        }
      // 将回调函数放入transaction的回调队列中
        if (callbacks) {
          for (var j = 0; j < callbacks.length; j++) {
            transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
          }
        }
      }
    }
    

          这个方法遍历所有的dirtyComponent,并根据渲染的顺序对它们进行排序,比如从父节点到子节点的开始更新。运行来自ReactReconciler对象下的performUpdateIfNecessary方法,并将回调函数放入transaction队列中。(修正:runBatchedUpdates的逻辑功能是先排序保证先渲染父组件再渲染子组件,再执行必要的更新,最后将回调函数写入callbackQueue。
    现在到最后一步了ReactReconciler
    2-3 ReactReconciler&performUpdateIfNecessary
          因为,internalInstance是ReactCompositeComponentWrapper实例,它的prototype继承了ReactCompositeComponent.Mixin,所以可以在ReactCompositeComponent下找到performUpdateIfNecessary这个方法,来看一看它的源码:

    performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
        // 组件的批量处理个数应该和当前或者接下来的批量处理个数相同
        if (internalInstance._updateBatchNumber !== updateBatchNumber) {
          return;
        }
        …
        internalInstance.performUpdateIfNecessary(transaction);
       …
      }
    };
    

    ReactCompositeComponent文件下找到performUpdateIfNecessary 方法:

      /**
       * If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate`
       * is set, update the component.
       *
       * @param {ReactReconcileTransaction} transaction
       * @internal
       */
      performUpdateIfNecessary: function (transaction) {
        if (this._pendingElement != null) {
          ReactReconciler.receiveComponent(
    this,
    this._pendingElement, 
    transaction, 
    this._context);
        } 
    else if (this._pendingStateQueue !== null || this._pendingForceUpdate) 
    {
         this.updateComponent(transaction, 
    this._currentElement, 
    this._currentElement,
     this._context, this._context);
        } else {
          this._updateBatchNumber = null;
        }
      }
    

    这个方法被分成了两个部分:
          (1) ReactReconciler.receiveComponent是用来在element级别中比较Component,所以element实例被比较后,如果component和element实例不同或者内容发生了变化,那么将会调用内部实例的receiveComponent方法。如果组件相同时,会调用updateComponent方法,这个方法包含了检测逻辑功能。
          (2) updateComponent 如果存在pending state,将会调用这个方法
    你可能会思考,为什么需要检测pending状态或者强迫更新,由于调用了setState,所以state处于pending状态?其实不是这样的。真正的原因是updateComponent是递归地更新的组件,但pending state是空的。而且,对_pendingElement的检查是用于处理children被更新的场景。下面来看一看updateComponent的具体实现方法:

    updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
        var inst = this._instance;
        !(inst != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Attempted to update component `%s` that has already been unmounted (or failed to mount).', this.getName() || 'ReactCompositeComponent') : _prodInvariant('136', this.getName() || 'ReactCompositeComponent') : void 0;
    
        var willReceive = false;
        var nextContext;
    
        // Determine if the context has changed or not
        if (this._context === nextUnmaskedContext) {
          nextContext = inst.context;
        } else {
          nextContext = this._processContext(nextUnmaskedContext);
          willReceive = true;
        }
    
        var prevProps = prevParentElement.props;
        var nextProps = nextParentElement.props;
    
        // Not a simple state update but a props update
        if (prevParentElement !== nextParentElement) {
          willReceive = true;
        }
    
        // An update here will schedule an update but immediately set
        // _pendingStateQueue which will ensure that any state updates gets
        // immediately reconciled instead of waiting for the next batch.
        if (willReceive && inst.componentWillReceiveProps) {
          if (process.env.NODE_ENV !== 'production') {
            measureLifeCyclePerf(function () {
              return inst.componentWillReceiveProps(nextProps, nextContext);
            }, this._debugID, 'componentWillReceiveProps');
          } else {
            inst.componentWillReceiveProps(nextProps, nextContext);
          }
        }
    
        var nextState = this._processPendingState(nextProps, nextContext);
        var shouldUpdate = true;
    
        if (!this._pendingForceUpdate) {
          if (inst.shouldComponentUpdate) {
            if (process.env.NODE_ENV !== 'production') {
              shouldUpdate = measureLifeCyclePerf(function () {
                return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
              }, this._debugID, 'shouldComponentUpdate');
            } else {
              shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
            }
          } else {
            if (this._compositeType === CompositeTypes.PureClass) {
              shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
            }
          }
        }
    
        if (process.env.NODE_ENV !== 'production') {
          process.env.NODE_ENV !== 'production' ? warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', this.getName() || 'ReactCompositeComponent') : void 0;
        }
    
        this._updateBatchNumber = null;
        if (shouldUpdate) {
          this._pendingForceUpdate = false;
          // Will set `this.props`, `this.state` and `this.context`.
          this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
        } else {
          // If it's determined that a component should not update, we still want
          // to set props and state but we shortcut the rest of the update.
          this._currentElement = nextParentElement;
          this._context = nextUnmaskedContext;
          inst.props = nextProps;
          inst.state = nextState;
          inst.context = nextContext;
        }
      },
    
    

    接下来我们一步步分析这个逻辑方法的步骤:
          (1) 首先,判断内容是否发生了变化,如果内容发生了变化将context,将nextUnmaskedContext经过this._processContext()方法处理后存储到nextContext
          (2) 检测props或者state是否发生改变,如果props发生改变,那么state没有更新,props有更新,则调用componentWillReceiveProps声明周期方法,更新props
          (3) _processPendingState来处理state更新,如果需要更新,则从_pendingStateQueue获取新的replace的标志位以及最新的state。
          (4) 判断component是否需要更新虚拟DOM,如果需要更新则调用_performComponentUpdate,否则只对props、state、context进行更新。
    最后来介绍之前说到的ReactUpdates文件中flushBatchedUpdates方法中的asp。

    if (asapEnqueued) {
          asapEnqueued = false;
          var queue = asapCallbackQueue;
          asapCallbackQueue = CallbackQueue.getPooled();
          queue.notifyAll();
          CallbackQueue.release(queue);
    }
    /**
     * Enqueue a callback to be run at the end of the current batching cycle. Throws
     * if no updates are currently being performed.
     */
    function asap(callback, context) {
      invariant(batchingStrategy.isBatchingUpdates, "ReactUpdates.asap: Can't enqueue an asap callback in a context where" + 'updates are not being batched.');
      asapCallbackQueue.enqueue(callback, context);
      asapEnqueued = true;
    }
    

          Asap的功能是使当前更新内容结束后调用asapCallbackQueue中的回调函数,如果没更新,则立即调用。
    修改:
    原理就说到这里了,乱的不得了,下面我们将梳理具体的流程。

    相关文章

      网友评论

          本文标题:React学习之(二)setState的更新机制

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