React@15.6.2源码解析---从 ReactDOM.render 到页面渲染(4)Transaction

188 阅读9分钟

英文注释翻译

React 中的 Transaction 创建一个黑盒环境来对方法进行封装,它能够封装任何方法,以便在调用方法之前和之后维护某些不变量(即使在调用封装的方法时抛出异常)。

任何实例化事务的人都可以在创建时提供不变量的实施者。“Transaction”类本身将为您提供一个额外的自动不变量(_isInTransaction)——任何事务实例在已经运行时不应该运行这个不变量。您通常会创建一个“事务”的单一实例,以便多次重用,该实例可能用于包装几个不同的方法。包装器非常简单——它们只需要实现两个方法。

使用范围

  1. 保存‘调和’前后的输入选择范围。恢复选择,即使在发生意外错误的情况下。
  2. 重排Dom时,禁用事件,避免引发多余的 blur/focus 事件,同时确保重排之后重新激活事件系统
  3. work线程调和后,由主线程更新UI
  4. 渲染完成之后,调用所有的componentDidUpdate
  5. (Future case)包装“ReactWorker”队列的特定刷新,以保存“scrollTop”(自动滚动感知DOM)。
  6. (Future case)DOM更新前后的布局计算。

事务性插件API

  1. 'initialize': 具有“初始化”方法的模块,该方法返回任何预计算。
  2. 'close': 以及接受预计算的“close”方法。“close”在包装的流程完成或失败时调用。

自定义Transaction: 通过将Tracaction中的属性通过Object.assign添加到自定义transaction的原型链中

个人理解

Transaction 对我们所要执行的方法进行一个封装,创建了一个黑盒环境。每个 Transaction 都会被一个或多个 wrapper 包裹,每一个 wrapper 都包含两个属性,一个是initialize 一个是 close,当 transaction执行的时候,首先会执行所有的initialize并添加异常机制,之后执行我们包装的方法,最后在执行所有的close并添加异常机制。

属性概述 _isInTransaction:Transaction是否在运行

reinitializeTransaction:初始化Transaction

getTransactionWrappers:获取包裹的wrapper

isInTransaction:判断当前是否在运行的方法

perform:Transaction的核心方法,用来执行包裹的method

inistializeAll:执行所有wrapperinitialize

closeAll:执行所有wrapperclose

自定义Transaction

当我们衍生出一个自定义Transaction时,只需要使用Object.assign将Transaction的所有方法添加到我们自定义的Transaction上,然后重写getTransactionWrappers方法。

官方给的一个图很形象

image_1dlr6lhv411hf178i13l51mk91ik99.png-98.7kB

wrappers是在Transaction被创建时注入的,实质上是通过上面说的通过Object.assign方法重写getTransactionWrappers方法得到包裹的wrappers,当运行perform方法时,首先执行的是所有的initialize之后是method最后是所有的close

Transaction

reinitializeTransaction

用于 Transaction 初始化,在实例化Transaction时,都执行了这个方法。

// Transaction.js
reinitializeTransaction: function () {
    this.transactionWrappers = this.getTransactionWrappers();
    if (this.wrapperInitData) {
      this.wrapperInitData.length = 0;
    } else {
      this.wrapperInitData = [];
    }
    this._isInTransaction = false;
  },

通过调用getTransactionWrappers方法,给Transaction实例添加transactionWrappers属性,之后附加wrapperInitData为空数组,并将运行标志_isInTransaction设为false

其中wrapperInitData数组是用来存储initialize的返回值的,用原文注释中的说法就是,存储initialize返回的预计算数据,在close方法中需要用到这些预计算,实质上就是用来判断是否出现了异常。

getTransactionWrappers

  // Transaction.js
  /**
   * @abstract
   * @return {Array<TransactionWrapper>} Array of transaction wrappers.
   */
  getTransactionWrappers: null,

这个属性用来获取Transaction外部的wrappers,在定义Transaction时,会通过Object.assign方法重写该属性

isInTransaction

  // Transaction.js
  isInTransaction: function () {
    return !!this._isInTransaction;
  },

判断当前Transaction是否在执行,使用!!两个感叹号,是可以做类型判断

perform

  // Transaction.js
  perform: function (method, scope, a, b, c, d, e, f) {
    /* eslint-enable space-before-function-paren */
    !!this.isInTransaction() ? /**/
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      // Catching errors makes debugging more difficult, so we start with
      // errorThrown set to true before setting it to false after calling
      // close -- if it's still set to true in the finally block, it means
      // one of these calls threw.
      // 先设置为 true 在调用 close 之后设置 false,如果最后还是 true 的话 那么中途出现了异常
      errorThrown = true;
      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        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.
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  },
  • Transaction的核心方法,用来执行包裹的method

  • 首先会判断当前Transaction是否在运行,是的话会报错

之后会将_isInTransaction设为true,表明该Transaction在执行

  • 调用initializeAll(0)参数0表示从第一个wrapper开始运行,0表示wrappers数组的下标

  • errorThrown 用来判断执行method期间是否出现了异常

  • 执行method

  • 执行所有的close

  • _isInTransaction 设为false 表明Transaction执行完成

  • 返回method 的返回值

initializeAll

  // Transaction.js 
  initializeAll: function (startIndex) {
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      try {
        // Catching errors makes debugging more difficult, so we start with the
        // OBSERVED_ERROR state before overwriting it with the real return value
        // of initialize -- if it's still set to OBSERVED_ERROR in the finally
        // block, it means wrapper.initialize threw.
        this.wrapperInitData[i] = OBSERVED_ERROR;
        this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
      } finally {
        if (this.wrapperInitData[i] === OBSERVED_ERROR) {
          // The initializer for wrapper i threw an error; initialize the
          // remaining wrappers but silence any exceptions from them to ensure
          // that the first error is the one to bubble up.
          try {
            this.initializeAll(i + 1);
          } catch (err) {}
        }
      }
    }
  },

执行所有的initialize方法,这边做了异常处理,异常逻辑为:将对应的wrapperInitData设为OBSERVED_ERROR,之后将initialize的返回值设为新的wrapperInitData,最后判断wrapperInitData的值,如果仍为OBSERVED_ERROR表明在执行initialize的过程中发生了异常。

closeAll

执行所有的close,其中的异常处理机制和initializeAll相类似

closeAll: function (startIndex) {
    !this.isInTransaction() ? /**/
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      var initData = this.wrapperInitData[i];
      var errorThrown;
      try {
        // Catching errors makes debugging more difficult, so we start with
        // errorThrown set to true before setting it to false after calling
        // close -- if it's still set to true in the finally block, it means
        // wrapper.close threw.
        errorThrown = true;
        if (initData !== OBSERVED_ERROR && wrapper.close) {
          wrapper.close.call(this, initData);
        }
        errorThrown = false;
      } finally {
        if (errorThrown) {
          // The closer for wrapper i threw an error; close the remaining
          // wrappers but silence any exceptions from them to ensure that the
          // first error is the one to bubble up.
          try {
            this.closeAll(i + 1);
          } catch (e) {}
        }
      }
    }
    this.wrapperInitData.length = 0;
  }

执行完之后将wrapperInitData清空。

ReactDefaultBatchingStrategyTransaction

ReactDefaultBatchingStrategy.js中定义了一个默认批处理策略事务。

// ReactDefaultBatchingStrategy.js

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

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  }
});

通过Object.assign将Transaction的属性添加到自定义Transaction的原型链中,并重写getTransactionWrappers方法,并且在实例化时,都会调用reinitializeTransaction方法进行初始化,这就是自定义一个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];

这个Transaction被两个wrapper包裹。他们的initialize方法都是空函数,那么关注点就转移到close方法,在该Transaction方法直接完之后,首先会将ReactDefaultBatchingStrategy.isBatchingUpdates设为false,之后就调用ReactUpdates.flushBatchedUpdates刷新更新队列。

ReactReconcileTransaction

调和事务

/**
 * Currently:
 * - The order that these are listed in the transaction is critical:
 * - Suppresses events.
 * - Restores selection range.
 *
 * Future:
 * - Restore document/overflow scroll positions that were unintentionally
 *   modified via DOM insertions above the top viewport boundary.
 * - Implement/integrate with customized constraint based layout system and keep
 *   track of which dimensions must be remeasured.
 *
 * @class ReactReconcileTransaction
 */
function ReactReconcileTransaction(useCreateElement) {
  this.reinitializeTransaction();
  // Only server-side rendering really needs this option (see
  // `ReactServerRendering`), but server-side uses
  // `ReactServerRenderingTransaction` instead. This option is here so that it's
  // accessible and defaults to false when `ReactDOMComponent` and
  // `ReactDOMTextComponent` checks it in `mountComponent`.`
  this.renderToStaticMarkup = false;
  this.reactMountReady = CallbackQueue.getPooled(null);
  this.useCreateElement = useCreateElement;
}

构造函数内部包含了三个属性

  • renderToStaticMarkup:只有在服务器端运行时用到

  • reactMountReady:通过CallbackQueue.getPooled获取,实质上返回一个CallbackQueue实例

  • useCreateElement:值为构造函数实例化时传入的参数

调和事务的属性稍微多了一点,通过混合Mixin注入原型链

var Mixin = {
  /**
   * @see Transaction
   * @abstract
   * @final
   * @return {array<object>} List of operation wrap procedures.
   *   TODO: convert to array<TransactionWrapper>
   */
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  },

  /**
   * @return {object} The queue to collect `onDOMReady` callbacks with.
   */
  getReactMountReady: function () {
    return this.reactMountReady;
  },

  /**
   * @return {object} The queue to collect React async events.
   */
  getUpdateQueue: function () {
    return ReactUpdateQueue;
  },

  /**
   * Save current transaction state -- if the return value from this method is
   * passed to `rollback`, the transaction will be reset to that state.
   */
  checkpoint: function () {
    // reactMountReady is the our only stateful wrapper
    return this.reactMountReady.checkpoint();
  },

  rollback: function (checkpoint) {
    this.reactMountReady.rollback(checkpoint);
  },

  /**
   * `PooledClass` looks for this, and will invoke this before allowing this
   * instance to be reused.
   */
  destructor: function () {
    CallbackQueue.release(this.reactMountReady);
    this.reactMountReady = null;
  }
};

这边提到了ReactUpdateQueue,也是一个重点。

外部包含了三个wrapper

var TRANSACTION_WRAPPERS = [SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING];

// 确保下拉框选择的数据在transaction后仍被选择
var SELECTION_RESTORATION = {
  /**
   * @return {Selection} Selection information.
   */
  initialize: ReactInputSelection.getSelectionInformation,
  /**
   * @param {Selection} sel Selection information returned from `initialize`.
   */
  close: ReactInputSelection.restoreSelection
};

// 抑制可能由于高级DOM操作(如临时从DOM中删除文本输入)而意外分派的事件(blur/focus)。
var EVENT_SUPPRESSION = {
  /**
   * @return {boolean} The enabled status of `ReactBrowserEventEmitter` before
   * the reconciliation.
   */
  initialize: function () {
    var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
    ReactBrowserEventEmitter.setEnabled(false);
    return currentlyEnabled;
  },

  /**
   * @param {boolean} previouslyEnabled Enabled status of
   *   `ReactBrowserEventEmitter` before the reconciliation occurred. `close`
   *   restores the previous value.
   */
  close: function (previouslyEnabled) {
    ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
  }
};

// 提供一个队列,来收集在 componentDidMount 和 componentDidUpdate 回调
// 在执行事务期间,遇到这两个生命周期,会将回调押入到队列中。在close的时候在去执行
var ON_DOM_READY_QUEUEING = {
  /**
   * Initializes the internal `onDOMReady` queue.
   */
  initialize: function () {
    this.reactMountReady.reset();
  },

  /**
   * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
   */
  close: function () {
    this.reactMountReady.notifyAll();
  }
};
_assign(ReactReconcileTransaction.prototype, Transaction, Mixin);

PooledClass.addPoolingTo(ReactReconcileTransaction);

再往下就是这两句,第一句是将Transaction,Mixin都添加到原型链中。

第二句是调用PooledClass.addPoolingTo用到了PoolClass属性。

PoolClass

PoolClass主要目的就是减少内存消耗。

// PoolClass.js
var PooledClass = {
  addPoolingTo: addPoolingTo,
  oneArgumentPooler: oneArgumentPooler,
  twoArgumentPooler: twoArgumentPooler,
  threeArgumentPooler: threeArgumentPooler,
  fourArgumentPooler: fourArgumentPooler
};
// PoolClass.js
var addPoolingTo = function (CopyConstructor, pooler) {
  // Casting as any so that flow ignores the actual implementation and trusts
  // it to match the type we declared
  var NewKlass = CopyConstructor;
  NewKlass.instancePool = [];
  NewKlass.getPooled = pooler || DEFAULT_POOLER;
  if (!NewKlass.poolSize) {
    NewKlass.poolSize = DEFAULT_POOL_SIZE;
  }
  NewKlass.release = standardReleaser;
  return NewKlass;
};

通过addPoolingTo方法给传入的构造函数添加instancePool属性和getPooled,以及一个释放池的函数NewKlass.release = standardReleaser,默认池大小为10。默认的getPooled是带一个参数的池函数。PoolClass提供了很多个池函数,他们的区别就是参数的多少,默认是一个参数的。

// PoolClass.js
var oneArgumentPooler = function (copyFieldsFrom) {
  var Klass = this;
  if (Klass.instancePool.length) {
    var instance = Klass.instancePool.pop();
    Klass.call(instance, copyFieldsFrom);
    return instance;
  } else {
    return new Klass(copyFieldsFrom);
  }
};

这边可以看到如果池子里有的话,那么就直接弹出一个然后重新实例化,没有的话则重新创建。使用池来储存,减少内存消耗。

所以这边看到getPooled方法那就是返回一个实例调用者的实例。

ReactUpdatesFlushTransaction

更新刷新事务,定义在ReactUpdates.js中,在执行ReactUpdates.flushBatchedUpdates时开启。

// ReactUpdates.js
function ReactUpdatesFlushTransaction() {
  this.reinitializeTransaction();
  this.dirtyComponentsLength = null;
  this.callbackQueue = CallbackQueue.getPooled();
  this.reconcileTransaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
}

_assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  },

  destructor: function () {
    this.dirtyComponentsLength = null;
    CallbackQueue.release(this.callbackQueue);
    this.callbackQueue = null;
    ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
    this.reconcileTransaction = null;
  },

  perform: function (method, scope, a) {
    // Essentially calls `this.reconcileTransaction.perform(method, scope, a)`
    // with this transaction's wrappers around it.
    return Transaction.perform.call(this, this.reconcileTransaction.perform, this.reconcileTransaction, method, scope, a);
  }
});

PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);

需要注意的是这边重写了perform方法。并且使用PoolClass.addPoolingTo添加了池属性。

外部包裹了两个wrapper

var NESTED_UPDATES = {
  initialize: function () {
    this.dirtyComponentsLength = dirtyComponents.length;
  },
  close: function () {
    if (this.dirtyComponentsLength !== dirtyComponents.length) {
      // Additional updates were enqueued by componentDidUpdate handlers or
      // similar; before our own UPDATE_QUEUEING wrapper closes, we want to run
      // these new updates so that if A's componentDidUpdate calls setState on
      // B, B will update before the callback A's updater provided when calling
      // setState.
      // 额外的更新是由于 componentDidUpdate 或其他类似的操作。
      // 在 update_queue wrapper close之前 我们希望运行这些新的更新以至于 如果 A 的componenDidUpdate 调用 B的 setState
      // 那么 B 会在 A 的updater提供的回调之前更新
      //
      // 在我们自己的UPDATE_QUEUEING包装器关闭之前,我们希望运行这些新更新
      // 以便如果A的componentDidUpdate在B上调用setState,那么B将在调用setState时提供回调A的updater之前更新。
      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];

需要注意的是。在第一个wrapperNESTED_UPDATES中的initialize函数,初始化当前实例的dirtyComponentsLength = dirtyComponents.length,之后在close的时候,发现他们两长度不一样了,说明在执行method时引起了额外的更新。这个时候就需要刷新这些新的更新。

而第二wrapperUPDATE_QUEUEING在会close的时候执行callbackQueue.notifyAll,这个方法是用来之前收集到的所有的回调。

这三个事务时目前我看到的三个,后面如果有其他的我会在加以分析。

总结

这一篇是介绍 React 中的Transaction, 以及目前三种自定义的事务内部的一些属性及包裹的wrapper