React 源碼深度解讀(七):事務 - Part 1

  • 前言

React 是一個十分龐大的庫,因爲要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,致使其代碼抽象化程度很高,嵌套層級很是深,閱讀其源碼是一個很是艱辛的過程。在學習 React 源碼的過程當中,給我幫助最大的就是這個系列文章,因而決定基於這個系列文章談一下本身的理解。本文會大量用到原文中的例子,想體會原汁原味的感受,推薦閱讀原文。javascript

本系列文章基於 React 15.4.2 ,如下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務 - Part 1
React 源碼深度解讀(八):事務 - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解java

  • 正文

在閱讀 React 源碼過程當中,transaction 能夠說無處不在,全部涉及到 UI 更新相關的操做都會藉助 transaction 來完成。下面,咱們就來看看它所起到的特殊所用。算法

  • Transaction 核心實現

Transaction 本質來講只是一個對象,它的核心方法是 perform:segmentfault

perform: function <
        A,
        B,
        C,
        D,
        E,
        F,
        G,
        T: (a: A, b: B, c: C, d: D, e: E, f: F) => G // eslint-disable-line space-before-function-paren
            >
        (
            method: T, scope: any,
            a: A, b: B, c: C, d: D, e: E, f: F,
        ): G {
            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.
                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;
        },

能夠看到,這個方法只作了 3 件事情:數組

  1. 執行初始化方法 initializeAll
  2. 執行傳入的 callback 方法
  3. 執行收尾方法 closeAll

這裏的結構頗有意思,有 try 居然沒有 catch,取而代之的是 finally。說明就算拋出了錯誤,finally 部分的代碼也要繼續執行,隨後再將錯誤往上層代碼拋。這樣能保證不管在什麼狀況下,closeAll 都能獲得執行。服務器

下面來看一下結構極其類似的 initializeAll 和 closeAll 方法:app

initializeAll: function (startIndex: number): void {
    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) {}
            }
        }
    }
},

...

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 {
            // 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;
},

transactionWrappers 是一個數組,一個 transaction 能夠有多個 wrapper,經過 reinitializeTransaction 來初始化。每一個 wrapper 都須要定義 initialize 和 close 方法。initializeAll 和 closeAll 都能保證其中一個 wrapper 成員拋出錯誤的時候,餘下的 wrapper 能繼續執行。initialize 有一個返回值,傳給對應的 close 方法。當 initialize 拋出錯誤的時候,因爲沒有 catch,exception 會一直往上拋,中斷了ret = method.call(scope, a, b, c, d, e, f)的執行去到 finally,接着執行 closeAll。異步

瞭解 transaction 的基本概念後,咱們來看下它是怎麼應用的。ide

  • ReactDefaultBatchingStrategyTransaction

咱們以ReactDefaultBatchingStrategyTransaction爲例子來看看 transaction 是怎麼用的:函數

// transaction 子類
function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}

// 覆蓋 transaction 的 getTransactionWrappers 方法
Object.assign(
  ReactDefaultBatchingStrategyTransaction.prototype,
  Transaction,
  {
    getTransactionWrappers: function() {
      return TRANSACTION_WRAPPERS;
    },
  }
);

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

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

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

首先,ReactDefaultBatchingStrategyTransaction繼承了 transaction,並覆蓋了getTransactionWrappers這個方法來定義本身的 wrapper。這 2 個 wrapper 很簡單,initialize都是空函數,close 的時候就重置下標誌位,而後再調用另外一個方法。

下面再看一下建立ReactDefaultBatchingStrategyTransaction的對象ReactDefaultBatchingStrategy

var transaction = new ReactDefaultBatchingStrategyTransaction();

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

第一步是建立一個 ReactDefaultBatchingStrategyTransaction 實例。當batchedUpdates第一次被調用的時候,alreadyBatchingUpdates爲 false,會調用transaction.perform,讓後續的操做都處於 transaction 的上下文之中。後面再調用batchedUpdates的時候,只是單純的執行callback

而調用ReactDefaultBatchingStrategy的是ReactUpdates,它經過依賴注入的方法在運行的時候將ReactDefaultBatchingStrategy注入進去。

function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent's
  // _renderValidatedComponent) assume that calls to render aren't nested;
  // verify that that's the case. (This is called by each top-level update
  // function, like setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)

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

  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

enqueueUpdate第一次執行的時候,它會檢測是否在 batchUpdate 的模式下(batchingStrategy.isBatchingUpdates),若是不是則調用batchingStrategy.batchedUpdates,若是是則執行dirtyComponents.push(component)

當咱們使用setState的時候,它會調用ReactUpdatesenqueueSetState,而後再調用enqueueUpdate。若是在 React 的生命週期函數又或者使用 React 自帶的合成事件時,會在setState以前先將整個處理過程設置爲 batchUpdate 的模式,因此當咱們setState的時候,實際上只會執行dirtyComponents.push(component),並不會立刻更新 state,這就是爲何setState看似異步更新的緣由。實際上它仍是同步的。

以 React 生命週期函數爲例子,當 Component 被初始化的時候,會執行_renderNewRootComponent

_renderNewRootComponent: function (
    nextElement,
    container,
    shouldReuseMarkup,
    context
) {
    ...

    // The initial render is synchronous but any updates that happen during
    // rendering, in componentWillMount or componentDidMount, will be batched
    // according to the current batching strategy.

    ReactUpdates.batchedUpdates(
        batchedMountComponentIntoNode,
        componentInstance,
        container,
        shouldReuseMarkup,
        context
    );

    ...
},

在這裏就預先將整個處理過程設置爲 batchUpdate 的模式了,官方的註釋也說明了這點。

  • 總結

咱們再經過一張圖,來總結下 transaction 是怎麼被調用的。

clipboard.png

clipboard.png

相關文章
相關標籤/搜索