React源碼解析-UI更新(Transaction)II

  • 一、前言

上一篇文章介紹了 transaction 的基本概念和用法。今天我們將講解在更新過程中,React 是如何通過多個 transacion 之間的協作,來有效組織代碼的。

  • 二、ReactUpdatesFlushTransaction

前文講到ReactDefaultBatchingStrategy close 的時候,會調用ReactUpdates.flushBatchedUpdates

PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);

var flushBatchedUpdates = function () {
    while (dirtyComponents.length || asapEnqueued) {
        if (dirtyComponents.length) {
            var transaction = ReactUpdatesFlushTransaction.getPooled();
            transaction.perform(runBatchedUpdates, null, transaction);
            ReactUpdatesFlushTransaction.release(transaction);
        }

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

這裏又調用了另一個 transaction 來處理後續的流程,有所不同的是 transaction 的創建不是直接 new,而是調用getPooled方法。這個方法是通過前面的PooledClass.addPoolingTo注入到ReactUpdatesFlushTransaction中的,如果對這個步驟感興趣可以看看這篇文章。下面來看ReactUpdatesFlushTransaction的內容:

Object.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) {
            return Transaction.perform.call(
                this,
                this.reconcileTransaction.perform,
                this.reconcileTransaction,
                method,
                scope,
                a
            );
        },
    }
);

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];

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.
            dirtyComponents.splice(0, this.dirtyComponentsLength);
            flushBatchedUpdates();
        } else {
            dirtyComponents.length = 0;
        }
    },
};

var UPDATE_QUEUEING = {
    initialize: function () {
        this.callbackQueue.reset();
    },
    close: function () {
        this.callbackQueue.notifyAll();
    },
};

ReactUpdatesFlushTransaction覆蓋了原型鏈上的perform方法,不是直接調用 callback,而是嵌套調用了this.reconcileTransaction.perform,在將 callback 透傳給reconcileTransactionperform。這裏的reconcileTransaction也開啓了實例池。

這裏要注意下NESTED_UPDATES這個 wrapper,如果dirtyComponents的數量跟 transaction 開始的時候不一樣,它又會遞歸調用flushBatchedUpdates,直到dirtyComponents不再變化爲止。UPDATE_QUEUEING這個 wrapper 暫時先忽略。

目前爲止的調用關係如下:

clipboard.png

  • 三、ReactReconcileTransaction

ReactReconcileTransaction是一個普通的 transaction,定義了一些 DOM 操作相關的 wrapper:

function ReactReconcileTransaction(useCreateElement: boolean) {
  this.reinitializeTransaction();
  this.renderToStaticMarkup = false;
  this.reactMountReady = CallbackQueue.getPooled(null);
  this.useCreateElement = useCreateElement;
}

var Mixin = {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },

  ...

  destructor: function() {
    CallbackQueue.release(this.reactMountReady);
    this.reactMountReady = null;
  },
};


Object.assign(ReactReconcileTransaction.prototype, Transaction, Mixin);

PooledClass.addPoolingTo(ReactReconcileTransaction);

var TRANSACTION_WRAPPERS = [
  SELECTION_RESTORATION,
  EVENT_SUPPRESSION,
  ON_DOM_READY_QUEUEING,
];

var SELECTION_RESTORATION = {
  /**
   * @return {Selection} Selection information.
   */
  initialize: ReactInputSelection.getSelectionInformation,
  /**
   * @param {Selection} sel Selection information returned from `initialize`.
   */
  close: ReactInputSelection.restoreSelection,
};

/**
 * Suppresses events (blur/focus) that could be inadvertently dispatched due to
 * high level DOM manipulations (like temporarily removing a text input from the
 * DOM).
 */
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);
  },
};

/**
 * Provides a queue for collecting `componentDidMount` and
 * `componentDidUpdate` callbacks during the transaction.
 */
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();
  },
};

這三個 wrapper 的作用註釋都講得很清楚了,不再贅述。值得一提的是這裏perform的 callback 是ReactUpdatesFlushTransaction透傳過來的ReactUpdate.runBatchedUpdates

目前爲止的調用關係如下:

clipboard.png

clipboard.png

  • 四、總結

到此爲止,transaction 相關的內容就講完了,下一篇開始介紹真正的更新操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章