One Last You Jen Bird
  1. 1 One Last You Jen Bird
  2. 2 Flower Of Life 发热巫女
  3. 3 Libertus Chen-U
  4. 4 Life Will Change Lyn
  5. 5 Time Bomb Veela
  6. 6 Hypocrite Nush
  7. 7 かかってこいよ NakamuraEmi
  8. 8 BREAK IN TO BREAK OUT Lyn
  9. 9 Last Surprise Lyn
  10. 10 Warcry mpi
2018-02-27 21:44:59

React源码解析——事务

setState可能是React最常用的API了,我们通过调用setState可以触发组件的更新,但调用后不能保证state会被同步更新。那么什么时候会同步更新,什么时候又是异步更新呢?这就和事务有关了。

setState的内部流程

先看源码对setState的定义:

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback);
  }
};

没有传入updater时,默认值为ReactNoopUpdateQueue,该值的enqueueCallback主要起到一个在非生产环境中警告的作用,没有实际意义,关键在于找出updater是在什么时候传入的。以ReactCompositeComponent类型组件为例,传入时刻是在构造时的_constructComponentWithoutOwner方法中:

return new Component(publicProps, publicContext, updatequeue);

可以看到updater就是updateQueue对象,接下来看其更新state的enqueueSetState方法:

enqueueSetState: function (publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

    if (!internalInstance) {
      return;
    }

    var queue = internalInstance._pendingStateQueue 
                || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  }

该方法通过getInternalInstanceReadyForUpdate方法获取当前组件实例的React内部表达,其包含了组件实例内部才存在的一些属性,我们只需要关注两个关键属性:待更新队列(_pengdingStateQueue)和更新回调队列(_pengdingCallbacks)。我们将当前待更新的State推入到queue中,再执行enenqueUpdate方法。

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

        dirtyComponents.push(component);
    }

该方法首先执行了ensureInjected方法,实际上就是一个保证ReactReconcileTransaction和batchingStraregy存在的验证方法,这两个属性下文会用到,如果不存在就会给出警告。接下来会根据batchingStrategy的值来决定是直接将组件实例放入dirtyComponents还是执行batchingStrategy.bathedUpdates。先看batchingStrategy的定义:

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

这是一种批量更新策略,其属性isBatchingUpdates表示是否处于批量更新中,默认值为false,将执行transaction的perform方法,为true时直接执行callback。接下来的就是setState更新的关键——事务(Transaction)了。

Transaction

首先看一下源码中的ASCII绘制的流程图:

/**
 * <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>
 */

该图形象的解释了事务的原理,其perform方法会给真正的方法包装wrapper,每个 wrapper都有initiallize和close方法,当执行真实的方法时会在之前和之后执行initialize和close。那么我们来看下transaction的定义:

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

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

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}

Object.assign(
  ReactDefaultBatchingStrategyTransaction.prototype,
  Transaction.Mixin,
  {
    getTransactionWrappers: function() {
      return TRANSACTION_WRAPPERS;
    },
  }
);

var transaction = new ReactDefaultBatchingStrategyTransaction();

可以看到RESET_BATCHED_UPDATED的wrapper会在事务结束时重置isBatchingUpdates为false。再梳理一下enqueueUpdate的流程:

  1. 最初执行时,isBatchingUpdates为false,将其置为true后执行perform方法。
  2. 开始事务的perform再次执行enqueueUpdate将组件推入dirtyComponents数组。
  3. 执行事务的close将isBatchingUpdates重置为false。

现在我们明白了RESET_BATCHED_UPDATES的作用,那么处在同一事务中的FLUSH_BATCHED_UPDATES又有什么用处呢?先看其源码:

var flushBatchedUpdates = function() {
  while (dirtyComponents.length) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }
    //......
  }
};

可以看到其遍历了所有的dirtyComponents,并在其中以事务ReactUpdatesFlushTransaction的方式执行runBatchedUpdates。

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

  for (var i = 0; i < len; i++) {
    var component = dirtyComponents[i];
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;
    //.....
    ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
    //.......
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance()
        );
      }
    }
  }
}

该方法首先将dirtyComponents以挂载顺序进行排序,为了保证父组件在其子组件之前更新,然后获取setState完成后的所有回调函数,再执行 ReactReconciler.performUpdateIfNecessary方法,该方法即用来触发组件的更新流程了,更新的详细流程在以后的文章中分析,现在需要知道的关键一点在于shouldComponentUpdate之前会执行_processPendingState来处理我们setState时创建的队列——pendingStateQueue。其源码如下:

  _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 = Object.assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      Object.assign(
        nextState,
        typeof partial === 'function' ?
          partial.call(inst, nextState, props, context) :
          partial
      );
    }

    return nextState;
  }

可以看到该函数会对同一生命周期内的state变化进行合并后再统一处理,如果setState传入对象会通过assign合并,如果传入函数会将最新的state和props传入并执行后返回的对象再通过assgin合并。

总结

综上,setState的更新是同步或异步的关键在于目前所处ReactDefaultBatching事务的状态。当该事务开启时会将state变化的组件存入数组,在事务结束时调用wrapper的close去关闭事务标志位并批量处理state和组件的更新。当该事务关闭时会立即开启该事务并同步的完成该事务,就是同步更新state的表现了。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「React」 标签。