React基础2--深入挖掘setState
众所周知,react通过this.state来访问,通过setState()方法来更新state。当this.setState()被调用的时候react会调用render重新渲染。
当执行setState的时候,会将需要更新的state合并到状态队列中去,这样可以高效的批量更新state。假如直接更改this.state的值那么state将不会被放到状态队列中去,当合并state队列的时候则忽略之前被修改的state,造成无法预知的错误。
首先,我们看一段setState源码
// 将setState事务放入队列中
ReactComponent.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
// 合并队列
enqueueSetState: function (publicInstance, partialState) {
// 获取ReactComponent组件对象
// publicInstance 指向 ReactComponent
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
// 合并队列
// 如果_pendingStateQueue为空,则创建它。可以发现队列是数组形式实现的
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
// 此处partialState 既是你传入的参数
queue.push(partialState);
// 将要更新的ReactComponent放入数组中
enqueueUpdate(internalInstance);
}
getInternalInstanceReadyForUpdate()
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
var internalInstance = ReactInstanceMap.get(publicInstance);
// 从map取出ReactComponent组件,
//mountComponent时把ReactElement作为key,将ReactComponent存入了map中,
//ReactComponent是React组件的核心,包含各种状态,数据和操作方法。而ReactElement则仅仅是一个数据类。
if (!internalInstance) {
if (process.env.NODE_ENV !== 'production') {
var ctor = publicInstance.constructor;
}
return null;
}
return internalInstance;
}
enqueueUpdate()
function enqueueUpdate(component) {
ensureInjected();
// 如果不是正处于创建或更新组件阶段,则处理update事务
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正在创建或更新组件,则暂且先不处理update,只是将组件放在dirtyComponents数组中
dirtyComponents.push(component);
}
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];
源码分析(我们看看从调用setState到组建更新发生了什么):
当我们调用setState方法时,实际上是执行enqueueSetState方法。
enqueueSetState方法接受两个参数,一个this指向ReactComponent组件对象,一个是我们设置的参数。
enqueueSetState方法接受参数后,会调用getInternalInstanceReadyForUpdate方法,该方法接受ReactComponent组件对象,然后从map中取出ReactComponent组件并返回。
如果返回不为空,则合并队列,如果_pendingStateQueue为空,则创建它,然后将我们设置的参数合并到一起。
最后调用enqueueUpdate方法。将更新的ReactComponent组件作为参数。
enqueueUpdate方法会先判断正否处于创建或更新组件阶段,如果是则暂且先不处理update,只是将组件放在dirtyComponents数组中。否则,将调用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。
到此,我相信认真读的童鞋,肯定有个疑问。。。
那就是transaction.perform(callback, null, a, b, c, d, e);里面的callback就是enqueueUpdate;
那么这样不就进入到死循环了么,怎么还调用updateComponent更新组件呢?带着疑问往下看。。
perform: function (method, scope, a, b, c, d, e, f) {
var errorThrown;
var ret;
try {
this._isInTransaction = true;
errorThrown = true;
// 先运行所有wrapper中的initialize方法
this.initializeAll(0);
// 再执行perform方法传入的callback
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// 最后运行wrapper中的close方法
try {
this.closeAll(0);
} catch (err) {}
} else {
// 最后运行wrapper中的close方法
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
initializeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
// 遍历所有注册的wrapper
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
// 调用wrapper的initialize方法
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
closeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
// 遍历所有wrapper
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) {
// 调用wrapper的close方法,如果有的话
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
}
通过transaction.perform的源码我们可以知道,perform先运行wrapper中的initialize方法,再执行perform方法传入的callback, 最后运行wrapper中的close方法。而在最后
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
wrapper中注册了两个wrapper:
FLUSH_BATCHED_UPDATES
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
flushBatchedUpdates ()
var flushBatchedUpdates = function () {
// 循环遍历处理完所有dirtyComponents
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
// close前执行完runBatchedUpdates方法,这是关键
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
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());
}
}
}
}
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);
}
},
到这一步是不是看到了receiveComponent 和 updateComponent有点欣喜若狂呢。
flushBatchedUpdates 方法会循环遍历处理完所有dirtyComponents,在close前执行完runBatchedUpdates方法。
runBatchedUpdates方法调用performUpdateIfNecessary();
最后进入update阶段。
setState循环调用风险
当调用setState时,实际上会执行enqueueSetState方法,对_pendingStateQueue合并,然后执行enqueueUpdate更新state。假如在componentWillUpdate调用setState此时,this._pendingStateQueue != null 所以,performUpdateIfNecessary方法会调用updateComponent方法更新组件,一次造成循环调用。
setState封装到异步函数里为什么就变成了同步的?
我们理一下setState的过程:
首先:enqueueUpdate方法
在该方法中通过判断batchingStrategy.isBatchingUpdates的值来决定如何处理。
我们在此处打印一下batchingStrategy.isBatchingUpdates
将flushBatchedUpdates
Paste_Image.pngPaste_Image.png Paste_Image.png
最后一步我们打印updateComponent里面的state更新后的state
Paste_Image.png假如我们在componentDidMount里面设置setState
componentDidMount(){
console.log('父组件didmount')
this.setState({num:this.state.num+2})
console.log(this.state.num)
console.log('ok')
}
结果为:
Paste_Image.png在enqueueUpdate方法里面batchingStrategy.isBatchingUpdates为true。代表正在穿件组件或更新,此时:
暂且先不处理update,只是将组件放在dirtyComponents数组中
dirtyComponents.push(component);
然后继续执行创建或更新组件。直到执行完didcomponent。
此时,打印this.state还是之前的,因为还没完成更新。
继续往下执行,直到updateComponent。在updateComponent里面
执行
var nextState = this._processPendingState(nextProps, nextContext);
state正式更新。
假如:
我们将setState封装到setTimeout函数里:
componentDidMount(){
console.log('父组件didmount')
setTimeout(()=>{
console.log('start')
this.setState({num:this.state.num+2})
console.log(this.state.num)
})
console.log(this.state.num)
console.log('ok')
}
执行结果:
Paste_Image.png因为setTimeout是异步的,所以componentDidMount会在继续执行,直到结束。此时
会执行ReactDefaultBatchingStrategy.isBatchingUpdates = false;
代表已经创建结束。
因此,在执行setState的时候batchingStrategy.isBatchingUpdates = false.
所以将执行
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
操作,进而执行
Paste_Image.png并且isBatchingUpdates被设置成 true,将组建设置成更新状态。
这时候react重新调用enqueueUpdate方法。然后继续执行后续操作。
将setState放到方法里执行
sets(){
this.setState({
value : true
})
console.log(this.setState.value)
}
render(){
return(
<div>
<button onClick={this.sets.bind(this)}>切换</button>
</div>
)
}
执行结果,点击切换后:
Paste_Image.png我们可以看到在最开始,无论是在周期函数,还是点击时间,都会调用
batchedUpdates方法;
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
console.log('batchedUpdates')
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
transaction.perform(callback, null, a, b, c, d, e);
}
}
设置 ReactDefaultBatchingStrategy.isBatchingUpdates = true;代表组件处于更新状态。
在结束都会调用
close: function () {
console.log('isBatchingUpdates = false')
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
设置 ReactDefaultBatchingStrategy.isBatchingUpdates = true.代表组件处于更新状态。
而在设置state的过程中,检测到组件处于更新状态或新建状态,则暂且先不处理update,只是将组件放在dirtyComponents数组中。所以要等到组件更新完成后在进行更新state,所以setState看起来像是异步的。但是将setState放到异步函数里面这时,组件状态已经处于完成更新或新建状态,所以setState不用等了直接就可以处理,所以像是同步的。
其他文章:
React基础1--生命周期详解
React基础3--diff算法