前面写过一篇谈论了用户操作到页面渲染的过程,相信大家对React的setState机制有了一定了解。这里我们看看setState在生命周期的各个流程里调用都会发生什么。
更新流程
结论:
-
componentWillReceiveProps
中安心调用,对state的改变会被合并,并且只刷新一次。 -
componentShouldUpdate
,componentWillUpdate
,render
,componentDidUpdate
中,可以调用,但是容易导致死循环,所以要做好条件判断。当然,如果非调用不可,官方只建议在componentDidUpdate
中调用。
componentWillReceiveProps中调用setState
还是先放上流程图:
首先我们知道React是在ReactUpdatesFlushTransaction
事务中进行更新操作的。
flushBatchedUpdates
更新新加入的。源码如下: 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) { ...... },});
然后我们看第一次更新都发生了什么?
首先会判断是否需要更新。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
有值,所以进入更新,调用updateComponent
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
,
updateComponent
。我们看到执行了 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,同时删除了pendingStateQueue
接下来就是componentShouldUpdate
,componentWillUpdate
,render
,componentDidUpdate
这些生命周期函数。perform执行完毕。
flushBatchedUpdates
接着又到了判断的逻辑 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.
coponentShouldUpdate,componentWillUpdate,render和componentDidUpdate
这几个和componentWIllReceiveProps有什么区别?最大的区别就在于。他们都是在_processPendingState
方法之后调用的。
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
。
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的操作讲解完毕