1. 程式人生 > >React原始碼分析5 — setState機制

React原始碼分析5 — setState機制

1 概述

React作為一門前端框架,雖然只是focus在MVVM中的View部分,但還是實現了View和model的繫結。修改資料的同時,可以實現View的重新整理。這大大簡化了我們的邏輯,只用關心資料流的變化,同時減少了程式碼量,使得後期維護也更加方便。這個特性則要歸功於setState()方法。React中利用佇列機制來管理state,避免了很多重複的View重新整理。下面我們來從原始碼角度探尋下setState機制。

2 setState和replaceState

我們都知道setState是以修改和新增的方式改變state的,不會改變沒有涉及到的state。而replaceState則用新的state完全替換掉老state。比如

this.state = {
  title: "example",
  desc: "this is an example for react"
};

setState({
  title: "new example"
});
console.log("setState: " + JSON.stringify(this.state)); // 1 

replaceState({
  title: "new example"
})
console.log("replaceState: " + JSON.stringify(this.state)); // 2

列印如下

setState: {"title"
:"new example","desc":"this is an example for react"} replaceState: {"title":"new example"}

可見,setState不會影響沒有涉及到的state,而replaceState則是完完全全的替換。下面讓我們進入原始碼來探尋究竟吧。

setState

setState方法入口如下

ReactComponent.prototype.setState = function (partialState, callback) {
  // 將setState事務放入佇列中
  this.updater.enqueueSetState(this
, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } };

取名為partialState,有部分state的含義,可見只是影響涉及到的state,不會傷及無辜。enqueueSetState是state佇列管理的入口方法,比較重要,我們之後再接著分析。

replaceState

replaceState: function (newState, callback) {
  this.updater.enqueueReplaceState(this, newState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'replaceState');
  }
},

replaceState中取名為newState,有完全替換的含義。同樣也是以佇列的形式來管理的。

3 enqueueSetState

enqueueSetState: function (publicInstance, partialState) {
    // 先獲取ReactComponent元件物件
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

    if (!internalInstance) {
      return;
    }

    // 如果_pendingStateQueue為空,則建立它。可以發現佇列是陣列形式實現的
    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    // 將要更新的ReactComponent放入陣列中
    enqueueUpdate(internalInstance);
}

其中getInternalInstanceReadyForUpdate原始碼如下,解釋都在程式碼註釋中

function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
  // 從map取出ReactComponent元件,還記得mountComponent時把ReactElement作為key,將ReactComponent存入了map中了吧,ReactComponent是React元件的核心,包含各種狀態,資料和操作方法。而ReactElement則僅僅是一個數據類。
  var internalInstance = ReactInstanceMap.get(publicInstance);
  if (!internalInstance) {
    return null;
  }

  return internalInstance;
}

enqueueUpdate原始碼如下

function enqueueUpdate(component) {
  ensureInjected();

  // 如果不是正處於建立或更新元件階段,則處理update事務
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  // 如果正在建立或更新元件,則暫且先不處理update,只是將元件放在dirtyComponents陣列中
  dirtyComponents.push(component);
}

enqueueUpdate包含了React避免重複render的邏輯。mountComponent和updateComponent方法在執行的最開始,會呼叫到batchedUpdates進行批處理更新,此時會將isBatchingUpdates設定為true,也就是將狀態標記為現在正處於更新階段了。之後React以事務的方式處理元件update,事務處理完後會呼叫wrapper.close(), 而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES這個wrapper,故最終會呼叫RESET_BATCHED_UPDATES.close(), 它最終會將isBatchingUpdates設定為false。這個過程比較麻煩,想更清晰的瞭解的話,建議自行分析原始碼。

故getInitialState,componentWillMount, render,componentWillUpdate 中setState都不會引起updateComponent。但在componentDidMount和componentDidUpdate中則會。

batchedUpdates: function (callback, a, b, c, d, e) {
  var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
  // 批處理最開始時,將isBatchingUpdates設為true,表明正在更新
  ReactDefaultBatchingStrategy.isBatchingUpdates = true;

  // The code is written this way to avoid extra allocations
  if (alreadyBatchingUpdates) {
    callback(a, b, c, d, e);
  } else {
    // 以事務的方式處理updates,後面詳細分析transaction
    transaction.perform(callback, null, a, b, c, d, e);
  }
}

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    // 事務批更新處理結束時,將isBatchingUpdates設為了false
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

4 事務transaction

事務通過wrapper進行封裝。一個wrapper包含一對initialize和close方法。比如RESET_BATCHED_UPDATES

var RESET_BATCHED_UPDATES = {
  // 初始化呼叫
  initialize: emptyFunction,
  // 事務執行完成,close時呼叫
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

transcation被包裝在wrapper中,比如

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

transaction是通過transaction.perform(callback, args…)方法進入的,它會先呼叫註冊好的wrapper中的initialize方法,然後執行perform方法中的callback,最後再執行close方法。擷取React程式碼中的一張圖如下

Markdown

下面分析transaction.perform(callback, args…)

  perform: function (method, scope, a, b, c, d, e, f) {
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      errorThrown = true;
      // 先執行所有wrapper中的initialize方法
      this.initializeAll(0);

      // 再執行perform方法傳入的callback
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        if (errorThrown) {
          // 最後執行wrapper中的close方法
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          // 最後執行wrapper中的close方法
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  },

  initializeAll: function (startIndex) {
    var transactionWrappers = this.transactionWrappers;
    // 遍歷所有註冊的wrapper
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      try {
        this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
        // 呼叫wrapper的initialize方法
        this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
      } finally {
        if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
          try {
            this.initializeAll(i + 1);
          } catch (err) {}
        }
      }
    }
  },

  closeAll: function (startIndex) {
    var transactionWrappers = this.transactionWrappers;
    // 遍歷所有wrapper
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      var initData = this.wrapperInitData[i];
      var errorThrown;
      try {
        errorThrown = true;
        if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) {
          // 呼叫wrapper的close方法,如果有的話
          wrapper.close.call(this, initData);
        }
        errorThrown = false;
      } finally {
        if (errorThrown) {
          try {
            this.closeAll(i + 1);
          } catch (e) {}
        }
      }
    }
    this.wrapperInitData.length = 0;
  }

5 runBatchedUpdates更新元件

前面分析到enqueueUpdate中呼叫transaction.perform(callback, args…)後,小夥伴們肯定會發現,callback還是enqueueUpdate方法啊,那豈不是死迴圈了?不是說好的setState會呼叫updateComponent,從而自動重新整理View的嗎?別急,我們還是要先從transaction事務說起。

我們的wrapper中註冊了兩個wrapper,如下

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

RESET_BATCHED_UPDATES用來管理isBatchingUpdates狀態,我們前面在分析setState是否立即生效時已經講解過了。那FLUSH_BATCHED_UPDATES用來幹嘛呢?

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

var flushBatchedUpdates = function () {
  // 迴圈遍歷處理完所有dirtyComponents
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      // close前執行完runBatchedUpdates方法,這是關鍵
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }

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

FLUSH_BATCHED_UPDATES會在一個transaction的close階段執行runBatchedUpdates,從而執行update。

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  dirtyComponents.sort(mountOrderComparator);

  for (var i = 0; i < len; i++) {
    // dirtyComponents中取出一個component
    var component = dirtyComponents[i];

    // 取出dirtyComponent中的未執行的callback,下面就準備執行它了
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;

    var markerName;
    if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      if (component._currentElement.props === component._renderedComponent._currentElement) {
        namedComponent = component._renderedComponent;
      }
    }
    // 執行updateComponent
    ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);

    // 執行dirtyComponent中之前未執行的callback
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
      }
    }
  }
}

runBatchedUpdates迴圈遍歷dirtyComponents陣列,主要幹兩件事。首先執行performUpdateIfNecessary來重新整理元件的view,然後執行之前阻塞的callback。下面來看performUpdateIfNecessary。

  performUpdateIfNecessary: function (transaction) {
    if (this._pendingElement != null) {
      // receiveComponent會最終呼叫到updateComponent,從而重新整理View
      ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
    }

    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      // 執行updateComponent,從而重新整理View。這個流程在React生命週期中講解過
      this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
    }
  },

最後驚喜的看到了receiveComponent和updateComponent吧。receiveComponent最後會呼叫updateComponent,而updateComponent中會執行React元件存在期的生命週期方法,如componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,render, componentDidUpdate。 從而完成元件更新的整套流程。

6 總結

setState流程還是很複雜的,設計也很精巧,避免了重複無謂的重新整理元件。它的主要流程如下

  1. enqueueSetState將state放入佇列中,並呼叫enqueueUpdate處理要更新的Component
  2. 如果元件當前正處於update事務中,則先將Component存入dirtyComponent中。否則呼叫batchedUpdates處理。
  3. batchedUpdates發起一次transaction.perform()事務
  4. 開始執行事務初始化,執行,結束三個階段
    1. 初始化:事務初始化階段沒有註冊方法,故無方法要執行
    2. 執行:執行setSate時傳入的callback方法,一般不會傳callback引數
    3. 結束:更新isBatchingUpdates為false,並執行FLUSH_BATCHED_UPDATES這個wrapper中的close方法
  5. FLUSH_BATCHED_UPDATES在close階段,會迴圈遍歷所有的dirtyComponents,呼叫updateComponent重新整理元件,並執行它的pendingCallbacks, 也就是setState中設定的callback。