异步情况下的setState()
import React,{Compontent} from 'react';
class Mydemo extends React.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 Mydemo;
结果如下:
react内部为了优化setState()的批处理,会对setState()进行合并,并且对相同属性的设置只保留最后一次的设置。
所以会打印按钮3、按钮6、按钮9
但第一次打印出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,{Compontent} from 'react';
class Mydemo extends React.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 Mydemo;
结果如下:
setState()同步、异步总结
异步的情况:
- 由React控制的事件处理函数,以及生命周期函数调用setState时表现为异步 。
- 大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等(合成事件中),这些事件处理函数中的setState都是异步处理的。
同步的情况:
- React控制之外的事件中调用setState是同步更新的。
- 比如原生js绑定的事件,setTimeout/setInterval,ajax,promise.then内等 React 无法掌控的 APIs情况下,setState是同步更新state的
setState()的参数接收
setState()可以接收一个对象外,还可以接收一个函数。
区别:
传递对象
批处理,对相同变量进行的多次处理会合并为一个,并以最后一次的处理结果为准传递函数
链式调用,React 会把我们更新 state 的函数加入到一个队列里面,然后,按照函数的顺序依次调用。
同时,为每个函数传入 state 的前一个状态,这样,就能更合理的来更新我们的 state 了,该函数有两个参数:prevState
props
传递函数时可以理解为:
本来我想拿到上一次setState() 执行完后的结果,需要使用一些特殊的方式,绕开合并逻辑,让setState() 保持本身的同步执行特性。
在异步执行的情况下,我还想拿到上一次setState执行完后的结果
,react给我们提供了方式,就是在setState()里面传入一个函数。
import React,{Compontent} from 'react';
class Mydemo extends React.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 + 2 }
});
console.log(this.state.count)
this.setState((state) => {
// 重要:在更新的时候读取 `state`,而不是 `this.state`。
return { count: state.count + 3 }
});
console.log(this.state.count)
}
render() {
return (
<div>
<button onClick={this.add}>按钮{this.state.count}</button>
</div>
)
}
}
export default Mydemo;
在setState中传一个函数,能拿到上次setState执行完后的结果,但是不妨碍是异步更新的,可以看到打印的是0,这是react给我们提供的方便之处。