在react中,通过管理状态来实现对组件的管理,通过this.state()来访问state,通过this.setState方法来更新state,当this.setState方法被调用时,react会重新调用render来重新渲染UI。
一、setState的全部实现过程:
1、enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component
2、如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。
3、batchedUpdates发起一次transaction.perform()事务
4、开始执行事务初始化,运行,结束三个阶段
初始化:事务初始化阶段没有注册方法,故无方法要执行
运行:执行setSate时传入的callback方法,一般不会传callback参数
结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法
5、FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。
二、setState异步更新
源码:
//将新的state合并到状态更新队列中
var nextState = this._processPendingState(nextProps,nextContext);
//根据更新队列和 shouldComponentUpdate的状态来判断是否需要更新组件
var shouldUpdate = this._pendingForceUpdate || ! inst. shouldComponentUpdate || inst. shouldComponentUpdate(nextProps, nextState, nextContext)
注意:如果不通过setState而直接修改this.state的值,而是诸如这样: this.state.value = 1,那么该state将不会被放入状态队列中,下次调用this.setState并对状态队列进行合并时,将会忽略之前直接别修改的state,因此我们应该用setState更新state的值。
三、setState的循环调用
React在setState之后,会经对state进行diff,判断是否有改变,然后去diff dom决定是否要更新UI。如果这一系列过程立刻发生在每一个setState之后,就可能会有性能问题。
当调用setState时,实际上会执行enqueueSetState方法,并对partialState以及_pendingStateQueue更新队列进行合并,最终通过enqueueUpdate执行state更新。
而performUpdateIfNecessary方法获取_pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用 reciveComponent 和updateComponent方法进行组件更新。
在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。
注意:setState不能在shouldComponentUpdate或componentWillUpdate中调用setState,则会造成循环调用,将浏览器的内存占满后崩溃。
四、事务 transaction
· transaction的使用场景:
1、在一次 DOM reconciliation(调和,即 state 改变导致 Virtual DOM 改变,计算真实 DOM 该如何改变的过程)的前后,保证 input 中选中的文字范围(range)不发生变化。
2、当 DOM 节点发生重新排列时禁用事件,以确保不会触发多余的 blur/focus 事件。同时可以确保 DOM 重拍完成后事件系统恢复启用状态。
3、当 worker thread 的 DOM reconciliation 计算完成后,由 main thread 来更新整个 UI
在渲染完新的内容后调用所有 componentDidUpdate 的回调
· 事务通过wrapper进行封装。
1、一个wrapper包含一对initialize和close方法。比如RESET_BATCHED_UPDATES
var RESET_BATCHED_UPDATES = {
// 初始化调用
initialize: emptyFunction,
// 事务执行完成,close时调用
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
2、transation 被包装在wrapper中,比如
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
transaction是通过transaction.perform(callback, args…)方法进入的,它会先调用注册好的wrapper中的initialize方法,然后执行perform方法中的callback,最后再执行close方法。
五、总结
在react中,根绝setState调用栈的不同,我们可以将它划分为两类,一类是在componentDidMount中,一类是在setTimeOut中。
这是一个例子:
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 次 打印 0
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 打印 0
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 打印 2
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 打印 3
}, 0);
}
render() {
return null;
}
};
代码运行过程:
this.setState(newState) ——newState存入_pendingStateQueue —— 是否处于batch update中
处于batch update中: component保存在dirtyComponents中
不处于batch update中:遍历dirtyComponents、调用updateComponent、更新state
在componentDidMount中调用setState时,batchingStrategy的isBatchingUpdates已经被设置为true了,所以setState的结果并没有立即生效,而是被放进了dirtyComponents中。所以前两次打印this.state.val都是0,因为新的state还没被应用到组件中。
setTimeOut中的两次setState,因为没有前置的batchedUpdate调用,所以batchingStrategy的isBatchingUpdates标志位是false,也就导致了新的state马上生效,没有走到dirtyComponents分支。也就是说,setTimeOut中的第一次执行,setState时,this.state.val为1,而setState完成后打印时this.state.val变成了2。第二次的setState同理,打印结果为3。