React@15.6.2源碼解析---從 ReactDOM.render 到頁面渲染(4)Transaction

英文注釋翻譯

React 中的 Transaction 創(chuàng)建一個(gè)黑盒環(huán)境來對(duì)方法進(jìn)行封裝,它能夠封裝任何方法,以便在調(diào)用方法之前和之后維護(hù)某些不變量(即使在調(diào)用封裝的方法時(shí)拋出異常)。

任何實(shí)例化事務(wù)的人都可以在創(chuàng)建時(shí)提供不變量的實(shí)施者?!癟ransaction”類本身將為您提供一個(gè)額外的自動(dòng)不變量(_isInTransaction)——任何事務(wù)實(shí)例在已經(jīng)運(yùn)行時(shí)不應(yīng)該運(yùn)行這個(gè)不變量。您通常會(huì)創(chuàng)建一個(gè)“事務(wù)”的單一實(shí)例,以便多次重用,該實(shí)例可能用于包裝幾個(gè)不同的方法。包裝器非常簡(jiǎn)單——它們只需要實(shí)現(xiàn)兩個(gè)方法。

使用范圍

  1. 保存‘調(diào)和’前后的輸入選擇范圍。恢復(fù)選擇,即使在發(fā)生意外錯(cuò)誤的情況下。
  2. 重排Dom時(shí),禁用事件,避免引發(fā)多余的 blur/focus 事件,同時(shí)確保重排之后重新激活事件系統(tǒng)
  3. work線程調(diào)和后,由主線程更新UI
  4. 渲染完成之后,調(diào)用所有的componentDidUpdate
  5. (Future case)包裝“ReactWorker”隊(duì)列的特定刷新,以保存“scrollTop”(自動(dòng)滾動(dòng)感知DOM)。
  6. (Future case)DOM更新前后的布局計(jì)算。

事務(wù)性插件API

  1. 'initialize': 具有“初始化”方法的模塊,該方法返回任何預(yù)計(jì)算。
  2. 'close': 以及接受預(yù)計(jì)算的“close”方法?!癱lose”在包裝的流程完成或失敗時(shí)調(diào)用。

自定義Transaction: 通過將Tracaction中的屬性通過Object.assign添加到自定義transaction的原型鏈中

個(gè)人理解

Transaction 對(duì)我們所要執(zhí)行的方法進(jìn)行一個(gè)封裝,創(chuàng)建了一個(gè)黑盒環(huán)境。每個(gè) Transaction 都會(huì)被一個(gè)或多個(gè) wrapper 包裹,每一個(gè) wrapper 都包含兩個(gè)屬性,一個(gè)是initialize 一個(gè)是 close,當(dāng) transaction執(zhí)行的時(shí)候,首先會(huì)執(zhí)行所有的initialize并添加異常機(jī)制,之后執(zhí)行我們包裝的方法,最后在執(zhí)行所有的close并添加異常機(jī)制。

屬性概述
_isInTransaction:Transaction是否在運(yùn)行

reinitializeTransaction:初始化Transaction

getTransactionWrappers:獲取包裹的wrapper

isInTransaction:判斷當(dāng)前是否在運(yùn)行的方法

perform:Transaction的核心方法,用來執(zhí)行包裹的method

inistializeAll:執(zhí)行所有wrapperinitialize

closeAll:執(zhí)行所有wrapperclose

自定義Transaction

當(dāng)我們衍生出一個(gè)自定義Transaction時(shí),只需要使用Object.assign將Transaction的所有方法添加到我們自定義的Transaction上,然后重寫getTransactionWrappers方法。

官方給的一個(gè)圖很形象

wrappers是在Transaction被創(chuàng)建時(shí)注入的,實(shí)質(zhì)上是通過上面說的通過Object.assign方法重寫getTransactionWrappers方法得到包裹的wrappers,當(dāng)運(yùn)行perform方法時(shí),首先執(zhí)行的是所有的initialize之后是method最后是所有的close

Transaction

reinitializeTransaction

用于 Transaction 初始化,在實(shí)例化Transaction時(shí),都執(zhí)行了這個(gè)方法。

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

通過調(diào)用getTransactionWrappers方法,給Transaction實(shí)例添加transactionWrappers屬性,之后附加wrapperInitData為空數(shù)組,并將運(yùn)行標(biāo)志_isInTransaction設(shè)為false。

其中wrapperInitData數(shù)組是用來存儲(chǔ)initialize的返回值的,用原文注釋中的說法就是,存儲(chǔ)initialize返回的預(yù)計(jì)算數(shù)據(jù),在close方法中需要用到這些預(yù)計(jì)算,實(shí)質(zhì)上就是用來判斷是否出現(xiàn)了異常。

getTransactionWrappers

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

這個(gè)屬性用來獲取Transaction外部的wrappers,在定義Transaction時(shí),會(huì)通過Object.assign方法重寫該屬性

isInTransaction

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

判斷當(dāng)前Transaction是否在執(zhí)行,使用!!兩個(gè)感嘆號(hào),是可以做類型判斷

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.
      // 先設(shè)置為 true 在調(diào)用 close 之后設(shè)置 false,如果最后還是 true 的話 那么中途出現(xiàn)了異常
      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的核心方法,用來執(zhí)行包裹的method。

  • 首先會(huì)判斷當(dāng)前Transaction是否在運(yùn)行,是的話會(huì)報(bào)錯(cuò)

之后會(huì)將_isInTransaction設(shè)為true,表明該Transaction在執(zhí)行

  • 調(diào)用initializeAll(0)參數(shù)0表示從第一個(gè)wrapper開始運(yùn)行,0表示wrappers數(shù)組的下標(biāo)

  • errorThrown 用來判斷執(zhí)行method期間是否出現(xiàn)了異常

  • 執(zhí)行method

  • 執(zhí)行所有的close

  • _isInTransaction 設(shè)為false 表明Transaction執(zhí)行完成

  • 返回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) {}
        }
      }
    }
  },

執(zhí)行所有的initialize方法,這邊做了異常處理,異常邏輯為:將對(duì)應(yīng)的wrapperInitData設(shè)為OBSERVED_ERROR,之后將initialize的返回值設(shè)為新的wrapperInitData,最后判斷wrapperInitData的值,如果仍為OBSERVED_ERROR表明在執(zhí)行initialize的過程中發(fā)生了異常。

closeAll

執(zhí)行所有的close,其中的異常處理機(jī)制和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;
  }

執(zhí)行完之后將wrapperInitData清空。

ReactDefaultBatchingStrategyTransaction

ReactDefaultBatchingStrategy.js中定義了一個(gè)默認(rèn)批處理策略事務(wù)。

// ReactDefaultBatchingStrategy.js

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

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

通過Object.assign將Transaction的屬性添加到自定義Transaction的原型鏈中,并重寫getTransactionWrappers方法,并且在實(shí)例化時(shí),都會(huì)調(diào)用reinitializeTransaction方法進(jìn)行初始化,這就是自定義一個(gè)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];

這個(gè)Transaction被兩個(gè)wrapper包裹。他們的initialize方法都是空函數(shù),那么關(guān)注點(diǎn)就轉(zhuǎn)移到close方法,在該Transaction方法直接完之后,首先會(huì)將ReactDefaultBatchingStrategy.isBatchingUpdates設(shè)為false,之后就調(diào)用ReactUpdates.flushBatchedUpdates刷新更新隊(duì)列。

ReactReconcileTransaction

調(diào)和事務(wù)

/**
 * 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;
}

構(gòu)造函數(shù)內(nèi)部包含了三個(gè)屬性

  • renderToStaticMarkup:只有在服務(wù)器端運(yùn)行時(shí)用到

  • reactMountReady:通過CallbackQueue.getPooled獲取,實(shí)質(zhì)上返回一個(gè)CallbackQueue實(shí)例

  • useCreateElement:值為構(gòu)造函數(shù)實(shí)例化時(shí)傳入的參數(shù)

調(diào)和事務(wù)的屬性稍微多了一點(diǎn),通過混合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,也是一個(gè)重點(diǎn)。

外部包含了三個(gè)wrapper

var TRANSACTION_WRAPPERS = [SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING];

// 確保下拉框選擇的數(shù)據(jù)在transaction后仍被選擇
var SELECTION_RESTORATION = {
  /**
   * @return {Selection} Selection information.
   */
  initialize: ReactInputSelection.getSelectionInformation,
  /**
   * @param {Selection} sel Selection information returned from `initialize`.
   */
  close: ReactInputSelection.restoreSelection
};

// 抑制可能由于高級(jí)DOM操作(如臨時(shí)從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);
  }
};

// 提供一個(gè)隊(duì)列,來收集在 componentDidMount 和 componentDidUpdate 回調(diào)
// 在執(zhí)行事務(wù)期間,遇到這兩個(gè)生命周期,會(huì)將回調(diào)押入到隊(duì)列中。在close的時(shí)候在去執(zhí)行
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都添加到原型鏈中。

第二句是調(diào)用PooledClass.addPoolingTo用到了PoolClass屬性。

PoolClass

PoolClass主要目的就是減少內(nèi)存消耗。

// 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方法給傳入的構(gòu)造函數(shù)添加instancePool屬性和getPooled,以及一個(gè)釋放池的函數(shù)NewKlass.release = standardReleaser,默認(rèn)池大小為10。默認(rèn)的getPooled是帶一個(gè)參數(shù)的池函數(shù)。PoolClass提供了很多個(gè)池函數(shù),他們的區(qū)別就是參數(shù)的多少,默認(rèn)是一個(gè)參數(shù)的。

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

這邊可以看到如果池子里有的話,那么就直接彈出一個(gè)然后重新實(shí)例化,沒有的話則重新創(chuàng)建。使用池來儲(chǔ)存,減少內(nèi)存消耗。

所以這邊看到getPooled方法那就是返回一個(gè)實(shí)例調(diào)用者的實(shí)例。

ReactUpdatesFlushTransaction

更新刷新事務(wù),定義在ReactUpdates.js中,在執(zhí)行ReactUpdates.flushBatchedUpdates時(shí)開啟。

// 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添加了池屬性。

外部包裹了兩個(gè)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之前 我們希望運(yùn)行這些新的更新以至于 如果 A 的componenDidUpdate 調(diào)用 B的 setState
      // 那么 B 會(huì)在 A 的updater提供的回調(diào)之前更新
      //
      // 在我們自己的UPDATE_QUEUEING包裝器關(guān)閉之前,我們希望運(yùn)行這些新更新
      // 以便如果A的componentDidUpdate在B上調(diào)用setState,那么B將在調(diào)用setState時(shí)提供回調(diào)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];

需要注意的是。在第一個(gè)wrapperNESTED_UPDATES中的initialize函數(shù),初始化當(dāng)前實(shí)例的dirtyComponentsLength = dirtyComponents.length,之后在close的時(shí)候,發(fā)現(xiàn)他們兩長(zhǎng)度不一樣了,說明在執(zhí)行method時(shí)引起了額外的更新。這個(gè)時(shí)候就需要刷新這些新的更新。

而第二wrapperUPDATE_QUEUEING在會(huì)close的時(shí)候執(zhí)行callbackQueue.notifyAll,這個(gè)方法是用來之前收集到的所有的回調(diào)。

這三個(gè)事務(wù)時(shí)目前我看到的三個(gè),后面如果有其他的我會(huì)在加以分析。

總結(jié)

這一篇是介紹 React 中的Transaction, 以及目前三種自定義的事務(wù)內(nèi)部的一些屬性及包裹的wrapper

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容