react经典面试题,看似简单,实际分析是比较复杂的。
声明:以下的所有分析适用于Legacy Mode与Blocking Mode,不确定是否适用于最新的Concurrent Mode,不了解的请移步react三种模式的功能比较
import React, { Component } from "react";
export default class extends Component {
state = {
count1: 1,
count2: 1
};
componentDidMount() {
this.setState({ count1: this.state.count1 + 1 });
this.setState({ count1: this.state.count1 + 1 });
setTimeout(() => {
this.setState({ count2: this.state.count2 + 1 });
this.setState({ count2: this.state.count2 + 1 });
}, 0);
}
render() {
return (
<div>
<h1>{this.state.count1}</h1>
<h1>{this.state.count2}</h1>
</div>
);
}
}
1.没有setTimeout的setState
this.setState({ count1: this.state.count1 + 1 });
this.setState({ count1: this.state.count1 + 1 });
第一次setState的调用栈
从图中可以发现excecutionContext不等于NoContext,所以关键函数flushSyncCallbackQueue是不会执行的
第二次setState的调用栈
与第一次一样关键函数flushSyncCallbackQueue没有执行
react每次setState都会生成一个update对象,两次setState生成的update通过next指针关联起来形成一个链表,fiber节点中updateQueue对象指向的就是这个副作用链表
而flushSyncCallbackQueue函数正是处理updateQueue的入口
第一次渲染后的componentDidMount(应该包括componentWillMount)生命周期中所有的setState都不会更新DOM,而是被合并进updateQuene中在最后由flushSyncCallbackQueue函数调用performSyncWorkOnRoot函数统一处理后更新DOM。
而更新实例state的函数的入口也是flushSyncCallbackQueue函数,所以在调用它之前实例的state并不会改变,下一次setState拿到的this.state.count依然是1。
2.有setTimeout的setState
在第一次渲染和处理完副作用链表之后会将excecutionContext置为0
每次都会调用flushSyncCallbackQueue函数,意味着调用setState都会更新实例state和更新对应DOM执行完这些后才会调用下一次的setState。
3.总结
react在第一次渲染后调用的componentDidMount(componentWillMount)生命周期中的setState都会合并成进updateQuene,然后批量处理相当于batching。个人理解:这样做的好处就是减少对DOM的操作,优化首屏速度,优化的代价就是产生setState数据不同步的问题,当然解决办法也很简单这里就不做阐述了。