前面写过一篇setState漫谈(一)谈论了用户操作到页面渲染的过程,相信大家对React的setState机制有了一定了解。这里我们看看setState在生命周期的各个流程里调用都会发生什么。
更新流程
结论:
-
componentWillReceiveProps
中安心调用,对state的改变会被合并,并且只刷新一次。 -
componentShouldUpdate
,componentWillUpdate
,render
,componentDidUpdate
中,可以调用,但是容易导致死循环,所以要做好条件判断。当然,如果非调用不可,官方只建议在componentDidUpdate
中调用。
componentWillReceiveProps中调用setState
还是先放上流程图:
首先我们知道React是在ReactUpdatesFlushTransaction
事务中进行更新操作的。
该事务perform之前会先报错dirtyComponents的length。
perform之后会将this.dirtyComponents长度与之前保存的进行对比。如果长度不一样,表示在这一轮更新中有setState被触发,新的dirtyComponent被加入序列。然后删除这一轮更新的dirtyComponent ,重新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
,
componentWillReceiveProps会调用setState方法。
setState,更新了pendIngStateQueue,更新了dirtyComponents。然后接着走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执行完毕。
接着是transaction的close方法。上面我们已经介绍过,因为updateComponent内部调用setState,导致dirtyComponent变了,因此又执行一轮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
方法之后调用的。
其他逻辑差不多,调用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的操作讲解完毕
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。