首页投稿(暂停使用,暂停投稿)@IT·互联网程序员

拆解setState[三][一源看世界][之React]

2016-07-11  本文已影响1062人  蛋先生DX

上一章节《拆解setState[二][一源看世界][之React]》讲到了更新过程最核心的方法flushBatchedUpdates,那我们接着聊

flushBatchedUpdates方法的源码中可以看出,它在ReactUpdatesFlushTransaction这个事务中执行了runBatchedUpdates方法,源码如下:

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  ...

  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.
  dirtyComponents.sort(mountOrderComparator);

  // Any updates enqueued while reconciling must be performed after this entire
  // batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
  // C, B could update twice in a single batch if C's render enqueues an update
  // to B (since B would have already updated, we should skip it, and the only
  // way we can know to do so is by checking the batch counter).
  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    // If a component is unmounted before pending changes apply, it will still
    // be here, but we assume that it has cleared its _pendingCallbacks and
    // that performUpdateIfNecessary is a noop.
    var component = dirtyComponents[i];

    // If performUpdateIfNecessary happens to enqueue any new updates, we
    // shouldn't execute the callbacks until the next render happens, so
    // stash the callbacks first
    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.props ===
        component._renderedComponent._currentElement
      ) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }

    ReactReconciler.performUpdateIfNecessary(
      component,
      transaction.reconcileTransaction,
      updateBatchNumber
    );

    if (markerName) {
      console.timeEnd(markerName);
    }

    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance()
        );
      }
    }
  }
}

这个方法遍历所有的dirty components,通过mount order进行排序(因为更新是从父级到子级),将所有setState的callback方法加入事务的队列,运行ReactReconcilerperformUpdateIfNecessary方法。所以我们得去看看ReactReconciler


ReactReconciler & performUpdateIfNeeded - 最后的步骤了

直接看源码实现吧

  performUpdateIfNecessary: function(
    internalInstance,
    transaction,
    updateBatchNumber
  ) {
    ...
    internalInstance.performUpdateIfNecessary(transaction);
    ...
  },

啊,原来是调用了internalInstance的方法,在上一章节中我们说过internalInstanceReactCompositeComponentWrapper实例,它的prototype继承了ReactCompositeComponent.Mixin,所以我们很容易就在ReactCompositeComponent找到了performUpdateIfNecessary这个方法,看下实现吧

  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;
    }
  },

这个方法分为两部分:

你可能在想有必要检查pending state或者force updates吗?state必须处于pending状态那是因为你调用了setState,对吗?不是滴,updateComponent是递归的所有你可以有更新的组件,但pending state是空的。同时对_pendingElement的检查是用于处理children被更新的场景。

updateComponent: function(
    transaction,
    prevParentElement,
    nextParentElement,
    prevUnmaskedContext,
    nextUnmaskedContext
  ) {
    var inst = this._instance;
    ...

    var willReceive = false;
    var nextContext;
    var nextProps;

    // Determine if the context has changed or not
    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context;
    } else {
      nextContext = this._processContext(nextUnmaskedContext);
      willReceive = true;
    }

    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) {
      ...
      inst.componentWillReceiveProps(nextProps, nextContext);
      ...
    }

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

    if (!this._pendingForceUpdate && inst.shouldComponentUpdate) {
      ...
      shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
      ...
    }

    ...

    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;
    }
  },

又是一个大方法!我们一步一步来解析它吧:

瞧一瞧_processPendingState

  _processPendingState: function(props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      assign(
        nextState,
        typeof partial === 'function' ?
          partial.call(inst, nextState, props, context) :
          partial
      );
    }

    return nextState;
  },

可以看出设置和替换state共享同个队列_pendingStateQueue,有一个属性_pendingReplaceState用于判断是否替换。如果是,pending state将合并replaced state;如果不是则合并当前的state。

从源码中也可以看出setState的第一个参数可以是个对象,也可以是一个函数,通过这个函数的入参可以拿到实例,当前最新的state, props和context,返回的是一个对象


ReactUpdates.asap

这是ReactUpdates的一个重要特性,在asap方法中实现

function asap(callback, context) {
  ...
  asapCallbackQueue.enqueue(callback, context);
  asapEnqueued = true;
}

它用在ReactUpdatesflushBatchedUpdates方法上,如:

if (asapEnqueued) {
  asapEnqueued = false;
  var queue = asapCallbackQueue;
  asapCallbackQueue = CallbackQueue.getPooled();
  queue.notifyAll();
  CallbackQueue.release(queue);
}

这个主要用在input elements上。一般情况调用callback的策略如下:所有的更新(包括嵌套的更新)完成后,Callbacks才被调用。Asap使callback可以在当前的更新完成后立即调用 - 所以如果有嵌套的更新,必须等待asap callbacks完成后才能继续


Wow,设置state真是一个好长好长的流程啊,有没昏昏欲睡的感觉,来个总结吧


最后,期待吐槽,期待指教!!!

--EOF--

上一篇下一篇

猜你喜欢

热点阅读