React事務的一些理解

ZHONGJIAFENG7發表於2019-01-26

學習React有一段時間了,剛接觸不久對於React神奇的操作很好奇,迫不及待看了原始碼看過幾遍原始碼總是一知半解,最近有時間再次學習React的相關知識,比如setState, componentDidMount等,意識到了之前被忽略提及的知識點,那就是React內部的事務,個人覺得事務很重要,生命週期中的componentWillMountcomponentDidMount, componentDidUpdate以及在一些生命週期中進行setState出現的一些出乎自己認知的結果,都和事務有很大的關係。

何為事務?

根據維基百科的解釋: 提供獨立可靠的恢復機制,保證出錯時資料的一致性,並且不同事務之間互相獨立。

事務一般在資料庫中使用的比較多,能保證出錯的時候進行rollbakc恢復。在React原始碼中作者給出了事務的一張明細圖能夠幫助較好的理解。React內部的事務分為三個階段initialize, method以及close階段,會在開始和結束時候分別遍歷transactionWrapper內部的所有初始化方法和close方法。

React事務的一些理解

有哪些事務?

React內部有個事務物件,能夠適配不同型別的事務:

Transaction.perform.call(this, this.reconcileTransaction.perform, this.reconcileTransaction, method, scope, a)
複製程式碼

主要先介紹2種事務,分別對應虛擬生命週期事務,狀態更新時的事務

批量更新事務(ReactDefaultBatchingStrategyTransaction)

React事務的一些理解

初始化進行元件掛載的時候會進行批量更新,批量更新方法會將ReactDefaultBatchingStrategy物件中的isBatchingUpdates設定為true,這也將導致後續加入的setState只會加入dirtyComponents中,在最後事務close的時候進行狀態合併,這也解釋了為何在componentDidMount中寫多個setState,最後輸出的狀態不是意料值。

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);
    }
  }
};
複製程式碼
// 將新的partialState加入到_pendingStateQueue以後的元件加到dirtyComponents
function enqueueUpdate(component) {
  ensureInjected();
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}
複製程式碼

首先是初始化遍歷執行emptyFunction,然後執行內部的被包裝的方法,這裡是batchedMountComponentIntoNode,顧名思義這個就是將虛擬DOM插入到真實節點下的方法,具體方法涉及到類別判斷來例項化不同型別的元件,遞迴插入等等不在本章的討論範圍,不過有一點需要注意的是在這個方法內部進行已新事物,我稱之為生命週期事務(ReactReconcileTransaction)。在最後close的階段呼叫flushBatchedUpdates方法進行將至更新完成,以及會將isBatchingUpdates設定為flase,這兩個close先後順序有一定講究,這也導致在這個事件或者componentDidMount中isBatchingUpdates的值始終為true,在最後結束才會為false。

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)
};
複製程式碼

更新事務(ReactUpdateFlushTransaction)

而上述說到的flushBatchedUpdates方法內部再次呼叫了一個新事務

React事務的一些理解

該事物我稱之為更新事務,主要和setState有關,過程中會呼叫runBatchedUpdates, 將callBack函式加入到佇列中,並將_pendingCallbacks的引用清空,關閉的時候的close方法主要是對dirtyComponents清空以及setState中回撥函式的通知

React事務的一些理解

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];

var NESTED_UPDATES = {
  initialize: function () {
    this.dirtyComponentsLength = dirtyComponents.length;
  },
  close: function () {
    if (this.dirtyComponentsLength !== dirtyComponents.length) {
      dirtyComponents.splice(0, this.dirtyComponentsLength);
      flushBatchedUpdates();
    } else {
      dirtyComponents.length = 0;
    }
  }
};

var UPDATE_QUEUEING = {
  initialize: function () {
    this.callbackQueue.reset();
  },
  close: function () {
    this.callbackQueue.notifyAll();
  }
};
複製程式碼

生命週期事務(ReactReconcileTransaction)

React事務的一些理解

if (inst.componentDidMount) {
  if ("development" !== 'production') {
    transaction.getReactMountReady().enqueue(function () {
      measureLifeCyclePerf(function () {
        return inst.componentDidMount();
      }, _this._debugID, 'componentDidMount');
    });
  } else {
    transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
  }
}
複製程式碼

在這裡開啟了一個事務,componentDidMount被放入了callbacks佇列中,當生命週期結束的時候會遍歷事務中的close方法,其中就有notifyAll方法遍歷callbacks進行輸出,而這裡的callback方法即為componentDidMount或者componentDidUpdate

React事務的一些理解

var TRANSACTION_WRAPPERS = [SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING];
var EVENT_SUPPRESSION = {
  initialize: function () {
    var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
    ReactBrowserEventEmitter.setEnabled(false);
    return currentlyEnabled;
  },

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

if ("development" !== 'production') {
  TRANSACTION_WRAPPERS.push({
    initialize: ReactInstrumentation.debugTool.onBeginFlush,
    close: ReactInstrumentation.debugTool.onEndFlush
  });
}
複製程式碼

總結

React事務的一些理解

總之,React真的每次看都會為內部的實現所驚歎,雖然是前端的一種框架,但還是感覺能學到很多思想和知識,寫的不對的地方還希望得到大牛們的指正。

相關文章