文章目录
1. React 中 setState 是同步还是异步 ? 如何控制 ?
setState是异步还是同步与react批量更新有很大关系,react为了优化性能,采用的是批量更新,状态队列更新机制
先看异步情况:
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 内部为了优化 setStae的批量处理,会对 setState 进行一个批量处理的原则,并且对相同的属性进行合并保留最后一次进行更新,所以按钮上面是 3 可以理解的
2.但是打印出来三次 0 这是为啥 ?
在 React
的 setState
函数实现中,会根据一个变量 isBatchingUpdates
判断是直接更新还是 this.state
还是放到一个 updateQueue
去延迟更新,而 isBatchingUpdates
默认是 false
表示 setStaet
会同步更新 this.state
但是,有一个函数,batchedUpdates
,改函数会把 isBatchingUpdates
修改为 true
而当 React
在调用事件处理函数之前就会先调用这个 batchedUpdates
将isBatchingUpdates
修改为true
,这样由 React 控制的事件处理过程 setState
不会同步更新 this.state
,而是异步的。
3.总结一下:
setState 本身是同步的
一但走了 react内部合并的一个逻辑
,放入了updateQueue
队列中就变成异步了
,而代码中的函数是react控制的,内部会走合并逻辑,所以这里的setState 不但是合并的也是异步的,所以打印出三个0
4.控制 setState 的同步异步:
上面说了 setState是异步的原因是因为走了react内部合并的一个逻辑 因此为了达到同步那只能绕开react内部合并的一个逻辑不让它进入 updateQueue那么它就成了同步的
因为setState()本身就是同步的
5.利用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
6.可以理解为:
本来我想拿到上一次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给我们提供的方便之处