美文网首页
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