React-setState源碼的理解

首先舉一個最簡單的例子:react

this.state={
    a:1
}
this.setState({
    a:2
})
console.log(this.state.a)
//1

能夠說setState()操做是一個異步,由於要將一段時間內的state改變壓入棧,並最終執行一次,同時也是優化性能的一部份數組

可是:app

定時器:異步

定時器中的setState,每次都會引發新的render,即便是同一個定時器中的屢次setState,函數

由於定時器中的回調不屬於react控制性能

 

原生事件:優化

 

 

 

下面簡單講解一下源碼的理解this

  

入口:spa

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

partialState:部分state,傳入enqueueSetState(),若是有回調函數,則調用enqueueCallback(),在更改後執行prototype

 

enquenueSetState:

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()獲取到ReactComponent組件對象,

判斷這個組件的state等待隊列是否爲空,爲空則建立爲空數組,並將state push進這個數組,

將要更新的組件傳入enqueueUpdate()並執行

 

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

publicInstance:enqueueSetState方法傳入的this,指當前組件實例

從map取出ReactComponent組件,還記得mountComponent時把ReactElement做爲key,

將ReactComponent存入了map中了吧,ReactComponent是React組件的核心,包含各類狀態,數據和操做方法。而ReactElement則僅僅是一個數據類

注:(virtual DOM 就是由一個個的React element 組成。React element, 是由React 庫提供的createElement 函數建立而來)

 

 

enqueueUpdate:

 

function enqueueUpdate(component) {
  ensureInjected();

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

  // 若是正在建立或更新組件,則暫且先不處理update,只是將組件放在dirtyComponents數組中
  dirtyComponents.push(component);
}

基本邏輯:若是如今是正在建立或者更新組件的階段,則把組件放入dirtyComponents數組中,並不先update,不然就進行batchedUpdates()

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:

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];

設置兩個wrapper,

RESET_BATCHED_UPDATES設置isBatchingUpdates,

 

 

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

 

事務transaction

不詳述,百度不少文章,大致思路:

 

  1. 初始化:事務初始化階段沒有註冊方法,故無方法要執行
  2. 運行:執行setSate時傳入的callback方法,通常不會傳callback參數
  3. 結束:更新isBatchingUpdates爲false,並執行FLUSH_BATCHED_UPDATES這個wrapper中的close方法

 

runBatchedUpdates:

 

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

執行updateComponent進行狀態更新,值得注意的是更新操做內會調用_processPendingState進行原有state的合併以及設置this._pendingStateQueue = null,這也就意味着dirtyComponents進入下一次循環時,執行performUpdateIfNecessary不會再去更新組件

 

 總結:

setState流程仍是很複雜的,設計也很精巧,避免了重複無謂的刷新組件。它的主要流程以下

  1. enqueueSetState將state放入隊列中,並調用enqueueUpdate處理要更新的Component
  2. 若是組件當前正處於update事務中,則先將Component存入dirtyComponent中。不然調用batchedUpdates處理。
  3. batchedUpdates發起一次transaction.perform()事務
  4. 開始執行事務初始化,運行,結束三個階段
  5. 初始化:事務初始化階段沒有註冊方法,故無方法要執行
  6. 運行:執行setSate時傳入的callback方法,通常不會傳callback參數
  7. 結束:更新isBatchingUpdates爲false,並執行FLUSH_BATCHED_UPDATES這個wrapper中的close方法
  8. FLUSH_BATCHED_UPDATES在close階段,會循環遍歷全部的dirtyComponents,調用updateComponent刷新組件,並執行它的pendingCallbacks, 也就是setState中設置的callback。

推薦完整地址:https://yq.aliyun.com/articles/72329?t=t1

相關文章
相關標籤/搜索