优质广告供应商

广告是为了更好地支持作者创作

react源码阅读笔记(3)batchedUpdates与Transaction

本文使用的是react 15.6.1的代码

在上篇文章组件的渲染中_renderNewRootComponent中最后调用了ReactUpdates.batchedUpdates函数,上文中只是简单的分析了最后实际是在内部调用了其中的第一个参数batchedMountComponentIntoNode,其实并没有那么简单,今天我们就来看看batchedUpdates实际做了些什么内容

ReactUpdates.batchedUpdates

//批量更新方法
function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected(); //不用理会,单纯的打印方法
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

实际上是调用了batchingStrategy.batchedUpdates中的方法,但是翻遍了整个文件,发现batchingStrategy这个对象一直是null,那么batchingStrategy是怎么初始化的呢?在ReactUpdates中,发现定义了这样一个对象ReactUpdatesInjection,其中代码如下

var ReactUpdatesInjection = {
  injectReconcileTransaction: function(ReconcileTransaction) {
    ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
  },

  injectBatchingStrategy: function(_batchingStrategy) {
    batchingStrategy = _batchingStrategy;
  },
};

其中有一个方法injectBatchingStrategy,那么是不是外部谁调用了该方法,使得最后batchingStrategy被注入进来了,在ReactDOM源码中,我们发现有一行

ReactDefaultInjection.inject(); //注入事件,环境变量,各种参数值

查看ReactDefaultInjection.inject()方法后,又找到了这样一行代码

ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);

再次查看ReactInjection中的源代码

var ReactInjection = {
  Component: ReactComponentEnvironment.injection,
  DOMProperty: DOMProperty.injection,
  EmptyComponent: ReactEmptyComponent.injection,
  EventPluginHub: EventPluginHub.injection,
  EventPluginUtils: EventPluginUtils.injection,
  EventEmitter: ReactBrowserEventEmitter.injection,
  HostComponent: ReactHostComponent.injection,
  Updates: ReactUpdates.injection,
};

也就是说,在ReactDOM之中,会执行到 ReactUpdates.injection.injectBatchingStrategy方法,同时,将ReactDefaultBatchingStrategy传入其中,所以最后batchingStrategy = ReactDefaultBatchingStrategy,那么回到前文batchingStrategy.batchedUpdates(callback, a, b, c, d, e),这里面的batchedUpdates实际就是执行的ReactDefaultBatchingStrategy中的方法
源码地址[ReactDefaultBatchingStrategy]

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  /**
   * Call the provided function in a context within which calls to `setState`
   * and friends are batched such that components aren't updated unnecessarily.
   */
  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // The code is written this way to avoid extra allocations
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);//重点代码
    }
  },
};

代码很简单,第一次调用的时候ReactDefaultBatchingStrategy.isBatchingUpdates肯定是false,那么就会调用transaction.perform方法,并将相应回调传入其中,那么我们来看看ReactDefaultBatchingStrategy相关代码


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();
}

// ReactDefaultBatchingStrategyTransaction.prototype添加Transaction对象的方法,如reinitializeTransaction,同时用新的函数覆盖getTransactionWrappers
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },
});

var transaction = new ReactDefaultBatchingStrategyTransaction();

发现transation是ReactDefaultBatchingStrategyTransaction的实例,但是perform方法是Transaction.js提供的,我们看看Transaction方法


'use strict';

var invariant = require('invariant');

var OBSERVED_ERROR = {};

/**
 * `Transaction` creates a black box that is able to wrap any method such that
 * certain invariants are maintained before and after the method is invoked
 * (Even if an exception is thrown while invoking the wrapped method). Whoever
 * instantiates a transaction can provide enforcers of the invariants at
 * creation time. The `Transaction` class itself will supply one additional
 * automatic invariant for you - the invariant that any transaction instance
 * should not be run while it is already being run. You would typically create a
 * single instance of a `Transaction` for reuse multiple times, that potentially
 * is used to wrap several different methods. Wrappers are extremely simple -
 * they only require implementing two methods.
 *
 * <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>
 *
 * Use cases:
 * - Preserving the input selection ranges before/after reconciliation.
 *   Restoring selection even in the event of an unexpected error.
 * - Deactivating events while rearranging the DOM, preventing blurs/focuses,
 *   while guaranteeing that afterwards, the event system is reactivated.
 * - Flushing a queue of collected DOM mutations to the main UI thread after a
 *   reconciliation takes place in a worker thread.
 * - Invoking any collected `componentDidUpdate` callbacks after rendering new
 *   content.
 * - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
 *   to preserve the `scrollTop` (an automatic scroll aware DOM).
 * - (Future use case): Layout calculations before and after DOM updates.
 *
 * Transactional plugin API:
 * - A module that has an `initialize` method that returns any precomputation.
 * - and a `close` method that accepts the precomputation. `close` is invoked
 *   when the wrapped process is completed, or has failed.
 *
 * @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
 * that implement `initialize` and `close`.
 * @return {Transaction} Single transaction for reuse in thread.
 *
 * @class Transaction
 */
var TransactionImpl = {
  reinitializeTransaction: function(): void {
    //获取wrappers
    this.transactionWrappers = this.getTransactionWrappers();
    if (this.wrapperInitData) {
      this.wrapperInitData.length = 0;
    } else {
      this.wrapperInitData = [];
    }
    this._isInTransaction = false;
  },

  _isInTransaction: false,

  /**
   * @abstract
   * @return {Array<TransactionWrapper>} Array of transaction wrappers.
   * 由具体Transaction来提供,如ReactDefaultBatchingStrategyTransaction
   */
  getTransactionWrappers: null,

  isInTransaction: function(): boolean {
    return !!this._isInTransaction;
  },

  perform: function<
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    T: (a: A, b: B, c: C, d: D, e: E, f: F) => G,
  >(method: T, scope: any, a: A, b: B, c: C, d: D, e: E, f: F): G {
    var errorThrown;
    var ret;
    try {
      //正在Transaction ing中
      this._isInTransaction = true;
     
      errorThrown = true;

      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        //如果initializeAll没有抛出异常的话
        if (errorThrown) {
          // If `method` throws, prefer to show that stack trace over any thrown
          // by invoking `closeAll`.
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          // Since `method` didn't throw, we don't want to silence the exception
          // here.
          //进入close生命周期
          this.closeAll(0);
        }
      } finally {
        // Transaction结束
        this._isInTransaction = false;
      }
    }
    return ret;
  },

  initializeAll: function(startIndex: number): void {
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      try {
       
        this.wrapperInitData[i] = OBSERVED_ERROR;
        //调用wrapper的initialize方法
        this.wrapperInitData[i] = wrapper.initialize
          ? wrapper.initialize.call(this)
          : null;
      } finally {
        //如果调用initialize有问题,则startIndex+1在调用
        if (this.wrapperInitData[i] === OBSERVED_ERROR) {
        
          try {
            this.initializeAll(i + 1);
          } catch (err) {}
        }
      }
    }
  },

 
  closeAll: function(startIndex: number): void {
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      var initData = this.wrapperInitData[i];
      var errorThrown;
      try {
      
        errorThrown = true;
        if (initData !== OBSERVED_ERROR && wrapper.close) {
          wrapper.close.call(this, initData);
        }
        errorThrown = false;
      } finally {
        if (errorThrown) {
        
          try {
            this.closeAll(i + 1);
          } catch (e) {}
        }
      }
    }
    this.wrapperInitData.length = 0;
  },
};

export type Transaction = typeof TransactionImpl;

module.exports = TransactionImpl;

代码比较少,从代码注释的简图看,在创建Transations的时候,会先注入wrapper,当调用perform方法的时候,会依次调用wrapper的initialize方法,随后在调用perform中传入的方法,最后在调用依次调用wrapper中的close方法,简单看下来,有设计模式(代理)的思路在里面,我们具体看perform的实现首先,调用了initializeAll方法,然后循环拿到transactionWrappers依次调用initialize方法,随后调用perform的第一参数,最后又调用closeAll方法,依次调用transactionWrappers中的close的方法,最后将_isInTransaction置为false表示transation结束。和图中描述的一样,也就是说,不同的Transation会去实现不同的wrapper来代理具体的方法,那么ReactDefaultBatchingStrategyTransaction的wrapper是什么?在ReactDefaultBatchingStrategy之中,实例化ReactDefaultBatchingStrategyTransaction时会调用
this.reinitializeTransaction()方法,该方法中调用了getTransactionWrappers获取具体的wrappers,对应的,ReactDefaultBatchingStrategyTransaction重写了getTransactionWrappers方法,因此,得到返回值
[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]
这是wrapper的实现

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

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

代码比较简单,两个wraper都只有close方法,根据代码描述,会先执行FLUSH_BATCHED_UPDATES的close方法也就是ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)

var flushBatchedUpdates = function() {
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      // 从缓存池中获取ReactUpdatesFlushTransaction对象
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      // 调用runBatchedUpdates
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }

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

react运行中当存在脏组件的时候,会用transaction调用runBatchedUpdates方法,在执行之前,react使用了对象池这样的优化手段来获取/生成transaction对象,该部分暂时跳过,后面在细读,我们在来看看runBatchedUpdates做了些什么

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

  // 排序,保证 dirtyComponent 从父级到子级的 render 顺序
  dirtyComponents.sort(mountOrderComparator);

  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    var component = dirtyComponents[i];

    // 获取该组件的回调数组
    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);
    }
    // 更新该 dirtyComponent 重点代码
    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(),
        );
      }
    }
  }
}

代码不多,首先是将脏components进行排序,变成从父组件到子组件的顺序排列,随后遍历组件调用performUpdateIfNecessary更新数据,同时将组件的回调压入回调队列中,等待最后调用,这里我们注意到并不是更新一个组件的时候就调用其回调,而是等到所有组件都更新完以后才去调用,看看核心代码performUpdateIfNecessary方法,内部就一行核心代码internalInstance.performUpdateIfNecessary(transaction)即调用了ReactComponent对象的performUpdateIfNecessary方法,即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方法,但是element以及content都是component自己的即_currentElement,_context
      this.updateComponent(
        transaction,
        this._currentElement,
        this._currentElement,
        this._context,
        this._context,
      );
    } else {
      this._updateBatchNumber = null;
    }
  }

绕了一圈,最后还是调用组件自己的updateComponent方法,而updateComponent会去执行React组件的生命周期,如componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render, componentDidUpdate。完成整套流程。后面我们慢慢介绍React的生命周期。

回到上面代码,刚才知道runBatchedUpdates是有事务去调用的,react事务会先执行wrapper中的init方法,在执行close方法,我们在来看看ReactUpdatesFlushTransaction的wrapper相关代码做了什么。


var NESTED_UPDATES = {
  initialize: function() {
    this.dirtyComponentsLength = dirtyComponents.length;
  },
  close: function() {
    // 在批量更新,如果有新的dirtyComponents被push,那么,需要再一次批量更新,从新加入的dirtyComponents开始
    if (this.dirtyComponentsLength !== dirtyComponents.length) {
      dirtyComponents.splice(0, this.dirtyComponentsLength);
      flushBatchedUpdates();
    } else {
      dirtyComponents.length = 0;
    }
  },
};

var UPDATE_QUEUEING = {
  initialize: function() {
    // 重置回调队列
    this.callbackQueue.reset();
  },
  close: function() {
    // 执行回调方法
    this.callbackQueue.notifyAll();
  },
};

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];

看到上面代码,不得不感叹react设计的巧妙,这里巧妙的使用了transition的生命周期,在initialize时获取当前dirtyComponent的个数,最后结束(close)的时候再次校验,如果数量不对,那么说明dirtyComponents有新增,再一次进行flushBatchedUpdates,因为更新操作是批量的,因此不会出现改一个就执行一次的情况,大大提高了性能,第二个wrapper主要是处理回调队列里面的内容,当更新处理完成后,最后调用UPDATE_QUEUEING close方法,调用notifyAll去执行所有dirtyComponent的回调函数队列

总结

注入的设计极大的方便了代码的维护,将实现给原子化,若以后版本升级,算法更新,可以直接切换injectiion中的原子换成新的版本即可。同时,高度抽象的代码也给阅读带来了困难

优质广告供应商

广告是为了更好地支持作者创作

推荐阅读更多精彩内容