ReactComponent 类的 setState方法
如下所示,setState方法是定义在 ReactComponent 类中的。
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
};
我们平常使用setState方法是这样的:
this.setState({},[callback])
首先,要如上这样的使用 setState方法就需要继承 ReactComponent类。所以,一般的React组件是这么定义的:
const App extends React.Component
React.Component 指代的就是 ReactComponent类。 因此新组件是继承了ReactComponent类。
上面的内容让我们知道了setState的来源,接下来我们就来看看setState是如何发挥作用的。
setState 做了什么?
setState 方法的作用很简单虎牙如下两个行核心代码。
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
this.updater
this.updater 是在组件实例化的时候赋予的。
ReactCompositeComponent 的 mountComponent 方法中,实例化了组件,并为其赋值:inst.updater = ReactUpdateQueue;
所以,this.updater 是 ReactUpdateQueue 模块。
this.updater.enqueueSetState
这里有必要说一下this这个值,this指代的就是当前组件的一个实例。
看一下 enqueueSetState 方法。
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
'
这里,如之前已经调用过replaceState的话,_pendingStateQueue就应该是有值的。否则就是没有值。
这里会将多个setState的值放在一个数组里 也就是为 _pendingStateQueue 属性赋值为,某次事件的全部 setState方法
'
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
'
调用 enqueueUpdate 方法,往下我们来看看 enqueueUpdate 方法
'
enqueueUpdate(internalInstance);
},
getInternalInstanceReadyForUpdate
getInternalInstanceReadyForUpdate 方法就是返回 组件的挂载实例。 也就是 instantiateReactComponent 方法 实例化的 组件。
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
var internalInstance = ReactInstanceMap.get(publicInstance);
return internalInstance;
}
enqueueUpdate
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
这个方法在 根组件挂载的文章中说过,我们这里再来看看
function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
这里就是为 dirtyComponents 放置了要更新的组件。
这里请参考文章
最终是调用了挂载实例的 performUpdateIfNecessary 方法来更新组件
performUpdateIfNecessary
performUpdateIfNecessary 方法我们来看看与 state相关的部分如下。
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}
如果 _pendingStateQueue 存在的haunt就调用 方法 this.updateComponent,也就是挂载实例的 updateComponent 方法。
关于该方法 请参考这篇文章 我们这里挑出关于state的部分来看看。
_processPendingState 方法
该方法来将多个setState 处理下:
_processPendingState: function (props, context) {
'
这里 queue 是要处理的state的队列。
nextState是初始情况下的 state信息。replace表示是否调用了replaceState方法。
这里生成了初始的 state
'
var nextState = assign({}, replace ? queue[0] : inst.state);
'
然后呢就遍历一下queue队列,拿出每一个 要改变的state,然后合并这些state。核心方法是 Assign方法。
简单的来说就是 将一个对象合并到另一个对象上。
如此,即使你调用一百次setState方法,最后,组件只会更新一次。
'
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
},
React组件中调用 setState 的时候,React会将多个state进行合并处理得到最后的state,而后只更新一次state来提升性能。融合state的过程其实简单的来说就是合并几个对象的过程。
setState的同步与异步的问题
如此,我们使用 this.handleState方法调用setState来修改count,和使用 setTimeout 来修改count。
this.state = {
count: 1
};
handleClick() {
this.handleState();
this.handleState();
this.handleState();
let self = this;
setTimeout(function() {
self.setState({
count: 12
});
}, 10000);
setTimeout(function() {
self.setState({
count: 15
});
}, 11000);
setTimeout(function() {
self.setState({
count: 18
});
}, 12000);
}
handleState() {
this.setState(
{
count: this.state.count + 1
},
() => {
console.log('asdasd')
}
);
}
虽然我们调用了三次 this.handleState 方法,但是,State最终只修改了一次,这里有一个合并state的过程。
而三次 setTimeout则是确确实实的执行了三次,state在页面上变更了三次。
出现上述状况的原因如下:
batchingStrategy.isBatchingUpdates 的值:
如果 batchingStrategy.isBatchingUpdates 的值为true的话,则会进行批处理,而如果batchingStrategy.isBatchingUpdates值为false的话,则不会采用批处理。
第一
点击事件触发 dispatchEvent(这里参考ReactEventListener模块的dispatchEvent方法) 方法,而后分发了绑定的事件。isBatchingUpdates 的值变为true( 这里使用了 ReactDefaultBatchingStrategy 模块的 批处理策略 )。
批处理事务开始执行( ReactDefaultBatchingStrategyTransaction)。
而后 执行了三个 this.handleState 方法,而后 三个setTimeout方法会等待执行(也就是Event Loop)
第二
三个 this.handleState 方法调用的时候调用了setState方法。而后就是将state一个个的入队也就是 ReactUpdateQueue.enqueueSetState 方法,来为 组件挂载实例的 _pendingStateQueue 赋值(这些值是三个setState方法的state)
第三
这个时候三个 this.handleState 执行完毕,组件被放置在dirtyComponent 队列里,批处理事务开始执行 close方法。
批处理事务在执行 close方法的时候开始了组件更新机制。遍历 dirtyComponent 里的每一项,而后对其进行 performUpdateIfNecessary 选择性更新。
在这个过程中state 会被合并。
第四
上述的这些执行完毕之后,组件以及更新了。 三个 setTimeout 方法 依次调用。这时候 batchingStrategy.isBatchingUpdates 的值是 false。也就是当前没有采用批处理策略。
然后在 ReactUpdate.enqueueUpdate 方法中,开始开启使用批处理模式(这里因为没有批处理,所以又开启了批处理,当然,开启的批处理是针对setTimeout方法的内部的所有操作state的方法而言的。而之前的没有使用批处理是针对setTimeout极其外部的那些内容而言的。批处理会使用批处理事务来处理。),然后组件挂载实例就被放置在 dirtyComponents中。
那挂载实例会被放在 dirtyComponents中,然后,批处理事务就执行dirtyComponent,更新组件。 以此类推,另外的setTimeout方法也是如此。
总结
因为事件触发的时候,React会一开始就默认使用批处理来处理事件内部的代码。于是,这个时候多个setState方法造成的state修改会被合并。
setTimeout和setInterval 则是跳过了当前事件流,在事件内的同步代码执行完毕之后(批处理事务 完成了组件的更新,而且将 isBatchinUpdate 置为false)setTimeout 方法才会开始执行。
setTimeout方法执行的时候,才会主动的采用批处理策略,这个批处理策略是针对 setTimeout方法的内部代码的。所以说,一个setTimeout方法内即使有多个setState方法,也只会更新一次组件。
如上,便是setState方法可以同步或者是异步执行的秘密。
能够造成setState同步执行的还有使用DOM2级事件流来添加事件(addEventListener、attachEvent)