setState 是同步更新还是异步更新?
大家好,我是爱吃鱼的桶哥Z。在React
16.8之前的版本中,我们更新数据需要用到setState
,那么你知道setState
是同步还是异步的呢?它内部是如何实现的,你了解吗?今天我们就一起来学习一下关于setState
的那些事吧!
setState
自从React
16.8添加了Hook
后,我们编写React
组件基本都是函数组件,很少用到class
组件了。我们都知道在函数组件中通过useState
这个Hook
来修改组件的状态,而在16.8之前的版本中,我们都是通过setState
来修改组件的状态,因此我们还是很有必要了解一下关于setState
相关的知识点。
在面试或者工作中,我们经常会遇到关于组件状态更新的问题,就拿setState
来说,在组件更新的时候,setState
是同步更新还是异步更新的?当我们更新一个组件的状态后,我们如何才能立即获取到刚才更新的状态?这些就是我们需要学习和了解的地方。要了解setState
是同步还是异步,就先需要了解一下React
中的合成事件
了。
如果了解过React
事件相关方面的童鞋,那么就会知道React
的事件都是合成事件
,而不是js
的原生事件。那什么是合成事件
呢?简单来说就是React
通过给document
上挂载一个事件监听函数,通过事件冒泡
的方式来完成事件的执行,当DOM
元素触发后会冒泡到document
,而React
就会找到对应的组件生成一个合成事件
出来,并按组件树模拟一遍事件冒泡
,这就是React
中的合成事件
。
当然,上述的方法在React
17之后的版本中进行了修改。React
17中将事件挂载在了DOM
的容器中,也就是挂载在React DOM
执行的节点上,这样修改的好处是哪怕一个项目中有多个版本的React
存在,组件的事件也不会乱套。那么哪些事件会被捕获生成合成事件
呢?在React
源码的事件快照中所包含的事件才会被捕获到,例如:click
、blur
、focus
等等。
了解完合成事件
,我们该继续学习setState
是同步还是异步的。一般来说setState
是异步的,例如下面这个例子:
clsss Test extends Component {
state = {
count: 0
};
componentDidMount() {
this.setState({
count: 1
}, () => {
console.log(this.state.count); // 1
});
console.log(this.state.count); // 0
}
render() {
return (
...
)
}
}
当我们在componentDidMount
生命周期中通过setState
修改组件的状态后,我们只有在setState
的第二个参数中才能立即获取到当前修改的值,而在外部获取到的值还是未改变之前的值,由此可以证明setState
是异步的。我们是否会觉得React
中的setState
执行像是一个队列,因为React
会根据队列逐一执行,并且合并setState
的操作,当state
数据完成后执行回调,然后根据结果来更新虚拟DOM
触发渲染。那么为什么React
官方团队要按这样的执行思路来实现呢?为什么不能用同步执行的思路来实现呢?
在React
17出来后,官方给出的解释的是:为了保持内部的一致性,如果setState
是同步的,但是props
却不是,就会导致数据的错乱;第二点就是为了后续的升级启用并发更新。那么什么情况下setState
是同步的呢?
如果我们将setState
放在setTimeout
或者setInterval
中,那么它们的执行就会跟上面将的完全不同了,大致的代码如下:
clsss Test extends Component {
state = {
count: 0
};
componentDidMount() {
this.setState({
count: this.state.count + 1
});
console.log(this.state.count); // 0
setTimeout(() => {
this.setState({
count: this.state.count + 1
});
console.log('setTimeout', this.state.count); // 1
}, 0);
}
render() {
return (
...
)
}
}
当setState
执行时,会将状态存入padding
队列中,然后React
会判断当前是否处于batch update
阶段,如果是,则会将组件存入dirtyComponents
中;反之则会遍历所有的dirtyComponents
,并调用updateComponent
用于更新pending
、state
或者props
。
在React
的生命周期事件和合成事件中可以获取到isBatchingUpdates
的控制权,用于将状态放入队列中,并控制执行的节奏。而在setTimeout
、addEventListener
这些原生事件中,无法获取到isBatchingUpdates
的控制权,就会导致isBatchingUpdates
只会为false
,且会一直执行,因此setState
就会同步执行并修改组件的状态。
最后
setState
其实并不是真的异步,只是看起来像是异步执行的,它是通过isBatchingUpdates
来判断当前执行是同步还是异步的,如果isBatchingUpdates
为true
,则按异步执行,反之就是同步执行。要改变isBatchingUpdates
,只需要打破React
的合成事件,在js
的原生事件中执行setState
即可,所以你知道setState
是同步还是异步的吗?
最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家
往期回顾
转载自:https://juejin.cn/post/7132654365541662728