【譯】了解React源代碼-UI更新(事務(wù))6

【譯】了解React源代碼-初始渲染(簡單組件)1
【譯】了解React源代碼-初始渲染(簡單組件)2
【譯】了解React源代碼-初始渲染(簡單組件)3
【譯】了解React源代碼-初始渲染(類組件)4
【譯】了解React源代碼-初始渲染(類組件)5
【譯】了解React源代碼-UI更新(事務(wù))6
【譯】了解React源代碼-UI更新(事務(wù))7
【譯】了解React源代碼-UI更新(單個DOM)8
【譯】了解React源代碼-UI更新(DOM樹)9


在某種程度上,復(fù)雜而有效的用戶界面更新是使React React的原因。 但是,在我們深入研究能夠?qū)崿F(xiàn)UI更新的眾所周知的機制(虛擬DOM和差異算法)之前,我們需要了解Transaction,它將控制權(quán)從高級API setState()轉(zhuǎn)移到那些底層處理邏輯。

本文中使用的文件:

renderers / shared / utils / Transaction.js
定義核心Transaction

renderers / shared / stack / reconciler / ReactDefaultBatchingStrategy.js
定義ReactDefaultBatchingStrategyTransaction及其API包裝器ReactDefaultBatchingStrategy

renderers / shared / stack / reconciler / ReactUpdates.js
定義使用ReactDefaultBatchingStrategyenqueueUpdate()

Unlike the previous posts that start from everyday APIs and move down the call stack. This post will take a bottom up approach.
與以前的文章不同,這些文章從日常的API開始并向下移動到調(diào)用棧。 這篇文章將采取自下而上的方法。

首先,我們來看一下

事務(wù)核心類

此類中唯一的事實上的“公共”方法是perform,它也提供其核心功能:

...
/**
...
   *
   * @param {function} method Member of scope to call.
   * @param {Object} scope Scope to invoke from.
   * @param {Object?=} a Argument to pass to the method.
   * @param {Object?=} b Argument to pass to the method.
   * @param {Object?=} c Argument to pass to the method.
   * @param {Object?=} d Argument to pass to the method.
   * @param {Object?=} e Argument to pass to the method.
   * @param {Object?=} f Argument to pass to the method.
   *
   * @return {*} Return value from `method`.
   */
  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 {
    /* eslint-enable space-before-function-paren */
...
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
...
      // one of these calls threw.
      errorThrown = true;
      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        if (errorThrown) {
...
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
...
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  },
...

TransactionImpl@renderers/shared/utils/Transaction.js

除了調(diào)用作為第一個參數(shù)傳遞給它的回調(diào)方法外,perform()只是1)在回調(diào)之前調(diào)用initializeAll(),然后在2)之后調(diào)用closeAll()。

在這里,errorThrown用來指示method.call()中發(fā)生的異常,在這種情況下,邏輯直接跳轉(zhuǎn)到finally塊,然后將errorThrown設(shè)置為false

接下來,我們來看一下perform()之前和之后調(diào)用的兩個方法的實現(xiàn):

...
  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;
        this.wrapperInitData[i] = wrapper.initialize
          ? wrapper.initialize.call(this)
          : null;
      } finally {
        if (this.wrapperInitData[i] === OBSERVED_ERROR) {
          try {
            this.initializeAll(i + 1);
          } catch (err) {}
        }
      }
    }
  },

...
  closeAll: function(startIndex: number): void {
...// scr: sanity check
    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;

TransactionImpl@renderers/shared/utils/Transaction.js

這兩個方法只是簡單地迭代this.transactionWrappers并分別調(diào)用其initialize()close()。

this.transactionWrappersTransaction的默認構(gòu)造函數(shù)中使用this.getTransactionWrappers()初始化:

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

TransactionImpl@renderers/shared/utils/Transaction.js

我們將很快看到this.transactionWrappers到底是什么。

這里的異常處理細節(jié)有些有趣。 以initializeAll()作為實例。 如果initialize()中發(fā)生異常,則finally塊(而不是catch)將處理其余this.transactionWrappers(即,從i +1transactionWrappers.length-1)的initialize()。 然后,異常會中斷for循環(huán)和整個initializeAll()邏輯,并一直處理到perform()initializeAll()的調(diào)用者)內(nèi)的finally塊,從而有效地跳過了

ret = method.call(scope, a, b, c, d, e, f);

在異常初始化的情況下。 最后,在相同的finally塊中調(diào)用closeAll()以完成事務(wù)。

現(xiàn)在我們知道本質(zhì)上什么是Transaction,但是它的作用是什么? 為了回答這個問題,我們以Transaction實例化為例,它是UI更新的事務(wù)入口點。

ReactDefaultBatchingStrategyTransaction

首先,ReactDefaultBatchingStrategyTransaction是實現(xiàn)getTransactionWrappers()Transaction的子類:

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

ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

其次,TRANSACTION_WRAPPERSthis.transactionWrappers的來源,它為上一節(jié)中使用的perform()提供了pre(initialize())和post(close())函數(shù)。

...
var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
}; // scr: -----------------------------------------------------> 2)

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
}; // scr: -----------------------------------------------------> 2)

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];  // scr: -------------------------------> 2)

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
} // scr: ------------------------------------------------------> 1)
...
  // scr: ------------------------------------------------------> 3)
var transaction = new ReactDefaultBatchingStrategyTransaction();
...

ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

1)在ReactDefaultBatchingStrategyTransaction的構(gòu)造函數(shù)中,調(diào)用超類Transaction的構(gòu)造函數(shù),該構(gòu)造函數(shù)使用2)中定義的FLUSH_BATCHED_UPDATES初始化this.transactionWrappers。

2)定義兩個包裝器以及它們各自的initialize()close(),它們在迭代FLUSH_BATCHED_UPDATES的循環(huán)中用在Transaction.initializeAll()Transaction.closeAll()

3)將ReactDefaultBatchingStrategyTransaction定義為單例。

最后,我們看一下ReactDefaultBatchingStrategy提供的公共API,可以從外界調(diào)用它

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  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) { // scr: --------> not applied here
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

ReactDefaultBatchingStrategy@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js

ReactDefaultBatchingStrategy作為batchingStrategy注入{第二篇 * 5}到ReactUpdates。 ReactUpdates.enqueueUpdate()使用ReactDefaultBatchingStrategy.batchedUpdates(),UI更新入口點setState()的基礎(chǔ)方法。

function enqueueUpdate(component) {
  ensureInjected();

  if (!batchingStrategy.isBatchingUpdates) { // scr: ----------> {a}
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  // scr: -----------------------------------------------------> 
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    // scr: this field is used for sanity check later
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js

這是我們在上一篇文章中看到的類似的遞歸技巧。

1)第一次輸入該方法時,ReactDefaultBatchingStrategy.isBatchingUpdatesfalse,這會觸發(fā)分支{a},該分支通向ReactDefaultBatchingStrategy.batchedUpdates();。

2)batchedUpdates()ReactDefaultBatchingStrategy.isBatchingUpdates設(shè)置為true,并初始化transaction;

3)batchedUpdatescallback參數(shù)是enqueueUpdate()本身,因此enqueueUpdate將再次使用transaction.perform()再次輸入。 請注意,兩個包裝器的前置方法(initialize())都是emptyFunction,因此兩次調(diào)用enqueueUpdate()之間什么都沒有發(fā)生。

4)當?shù)诙屋斎?code>enqueueUpdate()時(在剛剛初始化的Transaction上下文中),執(zhí)行分支;

...
dirtyComponents.push(component);
...

5)在enqueueUpdate()返回FLUSH_BATCHED_UPDATES的后方法(close())之后; 這是處理前面步驟中標記的所有dirtyComponents的主要方法

*8 we will come back to this FLUSH_BATCHED_UPDATES.close() and ReactUpdates.flushBatchedUpdates() in the next post
* 8我們將在下一篇文章中回到 FLUSH_BATCHED_UPDATES.close()ReactUpdates.flushBatchedUpdates()

6)最后,調(diào)用RESET_BATCHED_UPDATES的后方法(close()),這會將ReactDefaultBatchingStrategy.isBatchingUpdates設(shè)置為false并完成循環(huán)。

重要的是要注意,應(yīng)該在ReactDefaultBatchingStrategy.isBatchingUpdates:false的上下文中執(zhí)行3)和6)之間對enqueueUpdate()的任何后續(xù)調(diào)用,這意味著在這種情況下將使用分支。 所以就像

->dirtyComponents.push(component);
->dirtyComponents.push(component);
->dirtyComponents.push(component);
...
----->ReactUpdates.flushBatchedUpdates

Wrap-up

(原文鏈接Understanding The React Source Code - UI Updating (Transaction) VI)

(上一篇)【譯】了解React源代碼-初始渲染(類組件)5

(下一篇)【譯】了解React源代碼-UI更新(事務(wù))7

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

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