上上篇博客我讲了setState() 的批处理合并,而setState()是异步的还是同步的,和setState() 的批处理有很大的关系
,推荐先看完上上篇博客再来看这篇,会清晰很多
地址如下:
setState()批处理,合并策略,控制批处理----batchUpdates
setState()是异步的还是同步的?
先看异步的情况:
import React, {
Component } from 'react';
class com2 extends Component {
state = {
num: 0
}
add = () => {
this.setState({
num: this.state.num + 1
})
console.log(this.state.num)
this.setState({
num: this.state.num + 2
})
console.log(this.state.num)
this.setState({
num: this.state.num + 3
})
console.log(this.state.num)
}
render() {
return (
<div>
<button onClick={
this.add}>按钮{
this.state.num}</button>
</div>
);
}
}
export default com2;
结果如下:
之前说过,react内部为了优化setState()的批处理
,会对setState()进行合并
,并且对相同属性的设置只保留最后一次的设置
,所按钮上3
是可以理解的,
但是打印出三次0,那又是为啥?
在 React
的 setState
函数实现中,会根据一个变量 isBatchingUpdates
判断是直接更新 this.state
还是放到一个updateQueue
中延时更新,而 isBatchingUpdates
默认是 false
,表示 setState
会同步更新 this.state
;但是,有一个函数 batchedUpdates
,该函数会把 isBatchingUpdates
修改为 true
,而当 React
在调用事件处理函数之前就会先调用这个 batchedUpdates
将isBatchingUpdates
修改为true
,这样由 React 控制的事件处理过程 setState
不会同步更新 this.state
,而是异步的。
总结:
所以说setstate本身是同步的
,一旦走了react内部的合并逻辑
,放入了updateQueue
队列中就变成异步了
,而代码中的函数是react控制的,内部会走合并逻辑,所以这里的setState 不但是合并的也是异步的,所以打印出三个0
控制setState的同步和异步:
上面说了,setState是异步的原因是因为走了react内部的合并逻辑,那只要能绕过react内部的合并逻辑,不让它进入到updateQueue中不就变成同步了吗?
因为setState()本身就是同步的
利用setTimeout绕过react内部的合并逻辑
import React, {
Component } from 'react';
class com2 extends Component {
state = {
num: 0
}
add = () => {
//利用setTimeout绕过react的控制,不让setState()走合并逻辑
setTimeout(() => {
this.setState({
num: this.state.num + 1
})
console.log(this.state.num)
this.setState({
num: this.state.num + 2
})
console.log(this.state.num)
this.setState({
num: this.state.num + 3
})
console.log(this.state.num)
});
}
render() {
return (
<div>
<button onClick={
this.add}>按钮{
this.state.num}</button>
</div>
);
}
}
export default com2;
结果如下:
总结:
异步的情况:
由React控制的
事件处理函数,以及生命周期函数调用setState时表现为异步 。大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等(合成事件中),这些事件处理函数中的setState都是异步处理的。
同步的情况:
React控制之外的
事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval,ajax,promise.then内等 React 无法掌控的 APIs情况下
,setState是同步更新state的
值得一提的是
setState()可以接收一个对象外,还可以接收一个函数:
区别:
-
传递对象
批处理,对相同变量进行的多次处理会合并为一个,并以最后一次的处理结果为准 -
传递函数
链式调用,React 会把我们更新 state 的函数加入到一个队列里面,然后,按照函数的顺序依次调用。同时,为每个函数传入 state 的前一个状态,这样,就能更合理的来更新我们的 state 了,该函数有两个参数
-
prevState
-
props
-
这和我们前面理解的setState()异步执行不冲突
可以理解为:
本来我想拿到上一次setState() 执行完后的结果,需要使用一些特殊的方式,绕开合并逻辑,让setState() 保持本身的同步执行特性,代码如下:
import React, {
Component } from 'react';
class com2 extends Component {
state = {
count: 0
}
add = () => {
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)
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
})
}
render() {
return (
<div>
<button onClick={
this.add}>按钮{
this.state.count}</button>
</div>
);
}
}
export default com2;
点击后结果:
而现在,在异步执行的情况下,我还想拿到上一次setState执行完后的结果
,react给我们提供了方式,就是在setState()里面传入一个函数,代码如下:
import React, {
Component } from 'react';
class com2 extends Component {
state = {
count: 0
}
add = () => {
this.setState((state) => {
// 重要:在更新的时候读取 `state`,而不是 `this.state`。
return {
count: state.count + 1 }
});
console.log(this.state.count)
this.setState((state) => {
// 重要:在更新的时候读取 `state`,而不是 `this.state`。
return {
count: state.count + 1 }
});
console.log(this.state.count)
this.setState((state) => {
// 重要:在更新的时候读取 `state`,而不是 `this.state`。
return {
count: state.count + 1 }
});
console.log(this.state.count)
}
render() {
return (
<div>
<button onClick={
this.add}>按钮{
this.state.count}</button>
</div>
);
}
}
export default com2;
结果如下:
在setState中传一个函数,能拿到上次setState执行完后的结果,但是不妨碍是异步更新的,可以看到打印的是0,这是react给我们提供的方便之处