我们都知道setState是异步执行的;
事实上,setState本身的执行过程是同步的,只是因为在react的 合成事件与钩子函数 中它的执行顺序在更新之前,所以不能直接拿到更新后的值,形成了所谓的异步。
那么请问setState是什么时候是同步的呢?
事实上它在ajax、原生事件与setTimeout中是同步的。
今天主要是深入探讨一下关于setState为什么是异步的问题。
先来看一个例子。它是在面试里非常容易出现的一道题。
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = { count:0 }
}
increase=()=>{
this.setState({count:this.state.count+1});
console.log(this.state.count);
this.setState({count:this.state.count+1});
console.log(this.state.count);
setTimeout(()=>{
this.setState({count:this.state.count+1});
console.log(this.state.count);
this.setState({count:this.state.count+1});
console.log(this.state.count);
})
}
render() {
return (
<div>
<div>setState</div>
<button onClick={this.increase}>btn</button>
</div>
);
}
}
我们首先来分析一下它的整个过程哼,
因为onClick 是合成事件,其对应的函数 increase 中的 setState 会被放到 batch Update 的 queue 中做统一的更新,而且,当 state 中的属性名称相同时,后一次的 setState 会把上一次队列中的覆盖掉。
但是,异步任务中的 setState 并不会被放到 batch Update 的 queue 里面,所以里面的 setState 是同步的。
所以它的整个执行过程是这样的。
1、第一个 setState 放到 batchUpdateQueue 里。输出两个 count都是 0
2、第二个 setState 即将放到 queue 里面时,看到里面已经有一个 key = count 的 setState 了,于是,把那个 setState 替换掉。
3、setTimeout 整个被放到事件循环队列的宏任务队列里面
4、合成事件函数结束
5、batchUpdateQueue 批量执行 setState,count 此时为 1,因为 queue 里面只有一个 setState({count:this.state.count+1})。
6、count 此时被更新为 1
7、宏任务执行,同步执行 setState。
8、输出 2,3
总结:
- setState只有在和合成事件和钩子函数中是异步的;在原生事件和setTimeout中是同步的。
- setState的异步并不是说内部由异步代码实现,其本身的执行过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立即拿到更新之后的值,所以就形成了所谓的“异步”。
- setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState 多个不同的值,在更新时会对其进行合并批量更新。