源碼看React setState漫談(二)

前面寫過一篇setState漫談(一)談論了用戶操做到頁面渲染的過程,相信你們對React的setState機制有了必定了解。這裏咱們看看setState在生命週期的各個流程裏調用都會發生什麼。segmentfault

更新流程

結論:app

  1. componentWillReceiveProps中安心調用,對state的改變會被合併,而且只刷新一次。
  2. componentShouldUpdate,componentWillUpdate,render,componentDidUpdate中,能夠調用,可是容易致使死循環,因此要作好條件判斷。固然,若是非調用不可,官方只建議在componentDidUpdate中調用。

componentWillReceiveProps中調用setState

仍是先放上流程圖:
圖片描述函數

首先咱們知道React是在ReactUpdatesFlushTransaction事務中進行更新操做的。
該事務perform以前會先報錯dirtyComponents的length。
perform以後會將this.dirtyComponents長度與以前保存的進行對比。若是長度不同,表示在這一輪更新中有setState被觸發,新的dirtyComponent被加入序列。而後刪除這一輪更新的dirtyComponent ,從新flushBatchedUpdates更新新加入的。
源碼以下:this

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;
    }
  },
};
Object.assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },

  destructor: function() {
   .....
  },

  perform: function(method, scope, a) {
    ......
  },
});

而後咱們看第一次更新都發生了什麼?
首先會判斷是否須要更新。spa

performUpdateIfNecessary: function (transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
    } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
    } else {
      this._updateBatchNumber = null;
    }
  },

第一次進來this._pendingStateQueue有值,因此進入更新,調用updateComponentprototype

updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
    var inst = this._instance;

    var willReceive = false;
    var nextContext;

    var prevProps = prevParentElement.props;
    var nextProps = nextParentElement.props;

    ....
    inst.componentWillReceiveProps(nextProps, nextContext);    //將新的state合併到更新隊列中,此時nextState爲最新的state
    var nextState = this._processPendingState(nextProps, nextContext);
    var shouldUpdate = true;

    if (!this._pendingForceUpdate) {
      shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
      } 

    this._updateBatchNumber = null;
    if (shouldUpdate) {
      this._pendingForceUpdate = false;
      this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
    }
},

咱們看到進入updateComponent,而後執行componentWillReceiveProps,
componentWillReceiveProps會調用setState方法。
圖片描述
setState,更新了pendIngStateQueue,更新了dirtyComponents。而後接着走updateComponent
咱們看到執行了3d

var nextState = this._processPendingState(nextProps, nextContext);

_processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = _assign({}, replace ? queue[0] : inst.state);

    return nextState;
  },

能夠看到這裏根據pendingStateQueue,更新了state並賦給了nextState,同時刪除了pendingStateQueuecode

接下來就是componentShouldUpdate,componentWillUpdate,render,componentDidUpdate這些生命週期函數。perform執行完畢。
接着是transaction的close方法。上面咱們已經介紹過,由於updateComponent內部調用setState,致使dirtyComponent變了,所以又執行一輪flushBatchedUpdates
接着又到了判斷的邏輯component

performUpdateIfNecessary: function (transaction) {
    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
    } else {
      this._updateBatchNumber = null;
    }
  },

由於上面_pendingStateQueue已經被刪除,因此此次是不會觸發新一輪的update.orm

coponentShouldUpdate,componentWillUpdate,render和componentDidUpdate

這幾個和componentWIllReceiveProps有什麼區別?最大的區別就在於。他們都是在_processPendingState方法以後調用的。
其餘邏輯差很少,調用setState更新了pendIngStateQueue,更新了dirtyComponents....
可是這裏會到updateComponent方法之後沒有刪除‘pendingStateQueue’!
因此在close方法中,執行新一輪flushBatchedUpdates時,再次判斷performUpdateIfNecessary是須要更新的,所以又會觸發循環。這就形成了死循環!

細節補充

也許有人會問爲何setState對_pendingStateQueue的更新會同步到ReactCompositeComponent裏面。那咱們就來看看

首先,_pendingStateQueue來自enqueueReplaceState

var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
internalInstance._pendingStateQueue = [completeState];
internalInstance._pendingReplaceState = true;

其實這裏的internalInstance就是ReactElement對象。一路跟着代碼跟到ReactReconciler,一直到

internalInstance.performUpdateIfNecessary(transaction);

可咱們發現internalInstance並無performUpdateIfNecessary方法啊,其實這是定義在原型上的方法。咱們在instantiateReactComponent中發現了端倪:

Object.assign(
  ReactCompositeComponentWrapper.prototype,
  ReactCompositeComponent,
  {
    _instantiateReactComponent: instantiateReactComponent,
  },
);

因此,composite的調用者就是internalInstance,也就是咱們在調用棧裏傳來傳去的component,而咱們從頭至尾維護的也是internalInstance的屬性

加載流程

React將組件分爲三大類:

  • ReactEmptyComponent 空組件
  • ReactHostComponent 對原生HTML標籤的封裝
  • ReactCompositeComponent 用戶自定義組件

首先明確一點,通常操做componentWillMount和componentDidMount操做的都是自定義組件。因此這邊就主要看看自定義組件的加載流程。具體代碼見源碼的ReactCompositeComponent的mountComponent方法。

mountComponent: function(
    transaction,
    hostParent,
    hostContainerInfo,
    context,
  ) {
    ...
    var doConstruct = shouldConstruct(Component);
    var inst = this._constructComponent(
      doConstruct,
      publicProps,
      publicContext,
      updateQueue,
    );
    var renderedElement;
    
    ...
    
    ReactInstanceMap.set(inst, this);
    ...

    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    ...

    inst.componentWillMount();
     
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }

    var markup;
    markup = this.performInitialMount(
        renderedElement,
        hostParent,
        hostContainerInfo,
        transaction,
        context,
      );

     transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);

  
    const callbacks = this._pendingCallbacks;
    if (callbacks) {
      this._pendingCallbacks = null;
      for (let i = 0; i < callbacks.length; i++) {
        transaction.getReactMountReady().enqueue(callbacks[i], inst);
      }
    }

    return markup;
  },

大概的流程圖爲:
圖片描述

constructor,componentWillMount

constructor

能夠看到執行constructor以後纔將實例存入ReactInstanceMap而且初始化_pandingStateQueue
若是這時候調用setState,當進去ReactStateQueue時,

var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);

    if (!internalInstance) {
      return;
    }

嘗試從ReactInstanceMap取實例時將取不到任何值,這將致使直接返回。因此結果只會是,不但不會觸發從新渲染操做(固然這時候也沒東西能夠從新渲染),並且,state賦值也失敗。
這就是爲何contructor裏面給state賦值時直接寫

this.state = {
...
}

就能夠了。

componentWillMount

從源碼中能夠發現componentWillMount的下一步就進行了

this._processPendingState(inst.props, inst.context);

這個方法前面說活,就是合併了state的值。並將_pendingStateQueue序列設爲null.而後結果嘛固然是和componentWillUpdate同樣,不會觸發屢次渲染。
這裏有一點不同,請注意,這邊是直接把合併事後的state賦給了inst.state。也就是說compnentWillMount事後的操做state裏面的值已是最新的了。

componentDidMount

和componentDidUpdate同樣的緣由,也會觸發從新渲染。
固然,mount操做都是隻執行一次,就算從新渲染也是走的更新流程,因此能夠放心使用,不會形成更新流程中的死循環問題。

是什麼形成的從新渲染。

放一個更大的流程圖
圖片描述
看到了吧,再判斷組件類型以後,就會開啓一個事務進行加載。而這也是老熟人了ReactDefalutBatchingStragy。這個transaction在close方法中會執行flushBatchedUpdates
想不起來的能夠回過頭看看上面的更新流程。

至此,生命週期中對state的操做講解完畢

相關文章
相關標籤/搜索