源码看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的操做讲解完毕

相关文章
相关标签/搜索