一. 为什么使用setState##
- 直接更改this.state.counter不会引起界面刷新, 这是因为React不知道state发生了改变。
- React必须通过setState告诉React数据发生了改变。
- App继承了Component中的setState方法。
回溯源码 - Component类中,原型上设置了setState方法。
二. setState异步更新##
changeText () {
// 2. setState是异步更新
this.setState({
message: "你好啊,wwq",
})
console.log(this.state.message); // Hello World
}
- 上述打印结果为Hello,World,为未改变state前message的值。
- 这说明setState为异步更新,其还未执行的时候,已经在执行打印了,并不能在执行完setState之后立马拿到最新的state的结果。
三. 重点:setState异步原因##
- 异步可以显著的提升性能:如果每次调用setState都进行一次更新,意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的。最好的办法是获取到多个更新,之后进行批量更新;
- 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步,若不一致,则会产生很多问题。
四. 获取setState异步更新的数据##
- 给setState方法传递一个回调函数。
- 使用componentDidUpdate生命周期函数。
- 方法二调用要先于setState的回调函数。这个与源代码顺序有关。
五. setState同步更新的案例##
- 情况一:将setState放入定时器setTimeout中。
changeText() {
// 情况一:将setState放入定时器中
setTimeout(() => {
this.setState({
message: "你好啊,wec"
})
console.log(this.state.message);
}, 0)
}
- 情况二:用原生DOM事件绑定来监听click事件
componentDidMount(){
document.getElementById("btn").addEventListener("click", ()=>{
this.setState({
message: "你好啊,cpn"
})
console.log(this.state.message);
})
}
注意:这分成两种情况##
- 在组件生命周期或React合成事件中,setState是异步。如React的onClick是合成事件,和原生DOM的事件监听不一样。
- 在setTimeout或者原生DOM事件中,setState是同步。
- 两者上下文不同,在classComponentUpdater源码中会返回不同的时间。
注意:决定setState异步同步的条件##
回溯源码。
- 调用Component原型上setState时,执行如下代码:
this.updater.enqueueSetState(this, partialState, callback, 'setState');
- 在构造器中会传入updater对象,若没有传入,则默认为ReactNoopUpdateQueue(打印东西,无意义)。
- 真实开发中,每个组件都会有一个updater对象。
现在追溯到ReactBaseClasses源代码中。
能找到一个classComponentUpdater对象。其对应我们class中的updater。
它把setState放到enqueueSetState队列中。
该队列在之前Component源码中被调用,参数。
-
其获取了一个Fiber对象。
image.png
image.png 之后在computeExpirationForFiber方法中,将currentTime,Fiber还有另一个参数传入。
传入之后在其内部获取fiber.mode,根据当前执行上下文的不同情况来看方法的返回值。
根据不同的优先级,返回Sync或者Batchded。
再之后computeExpirationForFiber()的执行结果被传入createUpdate方法中。该方法记录了更新的方式,然后将该update对象传入enqueueUpdate中(实际为链表)等待处理。
然后调用scheduleWork(fiber, expirationTime),开始更新。

