本文使用的是react 15.6.1的代码
在react开发的时候,当我们没有使用flux,mobx,redux等状态管理管理工具的时候,总会调用this.setState这样一个api。可是你真的懂这个api吗?见过这样一个问题
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
问上述代码打印的val分别是多少?
答案是0、0、2、3,如果没有回答对也没有关系,我们来看看调用this.setState时到底发生了什么事情
首先先来看看setState的代码(代码位于ReactBaseClass):
/*
* @param {object|function} 下一个状态,会与当前的状态进行合并
* @param {?function} 当state更新后的回调函数callback
*/
ReactComponent.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
代码极其简单,就是调用了enqueueSetState方法,同时如果存在回调,调用enqueueCallback方法,
我们在来看看 this.updater.enqueueSetState(this, partialState)做了什么
在react源码阅读笔记(2)组件的渲染我们在mountComponent发现updater是通过 transaction.getUpdateQueue()来获取,在ReactReconcileTransaction中我们发现,最后返回的update就是ReactUpdateQueue.js中的ReactUpdateQueue,所以,来看看其中enqueueSetState方法
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
代码只有一行,继续跟进了解一下 ReactUpdates.enqueueUpdate方法
function enqueueUpdate(component) {
ensureInjected();
//若 batchingStrategy 不处于批量更新收集状态,则 batchedUpdates 所有队列中的更新
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 若 batchingStrategy 处于批量更新收集状态,则把 component 推进 dirtyComponents 数组等待更新
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
看到这里,尤其是batchingStrategy.batchedUpdates是不是很眼熟,我们在React源码阅读笔记(3)batchedUpdates与Transaction中介绍过,方法会在wrapper的close中调用flushBatchedUpdates更新项目,这里不在详细介绍了。
同时,根据代码得知,如果组件处于更新态,即isBatchingUpdates = true的时候,那么是不会调用更新方法的,而是将这个组件放入dirtyComponents组件队列中等待更新,当最后执行到close时,在去更新真正的state,这也解释了开头的问题,为什么在componentDidMount中setState不能实时的更新state的值,而setTimeout不会出现这种情况,因为componentDidMount时,isBatchingUpdates为true,处于更新中,而生命周期结束后,会在RESET_BATCHED_UPDATES这个wrapper中将isBatchingUpdates重置,因此setTimeout中调用state会实时更新state值。
总结
setState设计还是很巧妙的,同样巧妙的利用了transation,来完成更新的各个操作,同时,总结以前到现在的各个阶段看,react偏向于将数据缓存到队列中,最后一次性全部更新,这样好处是最后的绘制到dom上的数据一定是最新的,大大减少了绘制次数,提升了整体性能。缺点是如果刚上手react,可能会因为对state原理不清楚,使用了脏state,从而产生了不必要的麻烦。如果真要在调用setState后使用新的state,不妨使用this.setState这个api的第二个参数,传递一个回调函数进去,当全部更新完成后,在获取最新的state进行业务处理