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

上一章节《拆解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;
    }
  },

这个方法分为两部分:

  • ReactReconciler.receiveComponent - 在element级别去比较components。所以element实例比较后,如果它们不一样或者context改变了,就会触发internal instance的receiveComponent
  • this.updateComponent将被调用,当有pending state的情况

你可能在想有必要检查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;
    }
  },

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

  • 首先,先对context进行检查。如果改变了,context会被存进nextContext变量中。这里就不进行展开了。
  • updateComponent检查props更新或者只是state更新。如果props更新,则触发componentWillReceiveProps生命周期方法的执行
  • 接下来是处理当前最新的state。_processPendingState方法就是用来搞定这事的
  • 最后是判断component是否应该更新虚拟DOM,如果是,则通过_performComponentUpdate进行更新。如果不是,则仅仅是更新变量的值

瞧一瞧_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真是一个好长好长的流程啊,有没昏昏欲睡的感觉,来个总结吧

  • 调用setState,它把pending state change和callbacks都丢给ReactUpdateQueue处理
  • ReactUpdateQueue更新component的internal instance,把所有更新存放到internal instance的队列变量上,然后交给ReactUpdates
  • ReactUpdates利用batchingStrategy来保证所有state更新在一个事务中执行和刷新
  • flushBatchedUpdates负责同步地原子性地进行更新
  • ReactUpdatesFlushTransaction保证了嵌套的更新被正确地处理
  • runBatchedUpdates的职责是保证更新的顺序,即从父级到子级的顺序进行更新,然后调用ReactReconciler来更新components
  • performUpdateIfNecessary的功能是判断是prop还是state更新,然后调用updateComponent来处理更新
  • updateComponent区分更新的类型,检查shouldComponentUpdate的逻辑以判断是否要阻止虚拟DOM的更新。同时触发了一系列的生命周期方法(shouldComponentUpdatecomponentWillReceivePropscomponentWillUpdatecomponentDidUpdate
  • _processPendingState用于处理pending state并返回当前最新的state对象值。它可以区分是部分设置还是整个替换,并且处理了第一个参数的不同类型入参逻辑(object vs function)
  • 最后介绍了asap callbacks,用于解决输入类型组件更新state的问题 - 它可以让callback在组件更新后立即执行,而不用等到所有嵌套组件完成更新才执行

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

--EOF--

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容