最近发现一个场景:请求的回调里setState时,setState后面的同步代码会在render完之后才会执行,而我的setState并前没有加await关键词变成同步的。
场景一:click调用setState
constructor(props) {
super(props);
this.state = {
name:1
}
}
click(){
let _name = this.state.name
console.log('click before')
this.setState({
name:++_name
},()=>{
console.log('click 回调')
})
console.log('click behind')
}
render() {
console.log('render')
return (
<div onClick={this.click.bind(this)}>
{this.state.name}
</div>
)
}
复制代码
执行结果
click before
click behind
render
click 回调
复制代码
解析
这种情况最常见,先执行同步代码,setState后触发render, render后会执行setState的回调,类似于vm.$nextTick()
场景二:click调用await setState
constructor(props) {
super(props);
this.state = {
name:1
}
}
async click(){
let _name = this.state.name
console.log('click before')
await this.setState({
name:++_name
},()=>{
console.log('click 回调')
})
console.log('click behind')
}
render() {
console.log('render')
return (
<div onClick={this.click.bind(this)}>
{this.state.name}
</div>
)
}
复制代码
执行结果
click before
render
click 回调
click behind
复制代码
解析
先执行同步部分输出click before,因为setState被await了,所以setState后面同步代码不会执行,等setState执行完后,先执行render,在执行setState的回调,最后执行await后面的同步代码。类似于await new Promise().then的形式:
console.log(0)
await new Promise((resolve)=>{
resolve(1)
this.setState({
name:++_name
},()=>{
console.log(1.5)
})
}).then(res=>{
console.log(res)
})
console.log(2)
输出结果
0
render
1.5
1
2
复制代码
场景三:异步回调里调用setState
constructor(props) {
super(props);
this.state = {
name:1
}
// this.getData()
}
componentWillMount(){
// this.getData()
}
componentDidMount(){
// this.getData()
}
getData(){
fetch('/api',{
method: 'post',
credentials: 'same-origin',
headers:{
'Content-Type':'application/json',
},
body:JSON.stringify({
})
})
.then(res=>res.json())
.then(res=>{
console.log('send before')
this.setState({
name:1000
},()=>{
console.log('send 回调')
})
console.log('send behind')
})
Promise.resolve().then(()=>{
console.log('send before')
this.setState({
name:1000
},()=>{
console.log('send 回调')
})
console.log('send behind')
}).then(()=>{
console.log('send before')
this.setState({
name:1000
},()=>{
console.log('send 回调')
})
console.log('send behind')
})
setTimeout(()=>{
console.log('send before')
this.setState({
name:1000
},()=>{
console.log('send 回调')
})
console.log('send behind')
})
}
render() {
console.log('render')
return (
<div onClick={this.getData.bind(this)}>
{this.state.name}
</div>
)
}
复制代码
执行结果
以上不论是click触发getData方法,还是constructor、componentWillMount、componentDidMount里调用,只要setState在一个异步回调里执行,输出的结果都一样
render
send before
render
send 回调
send behind
复制代码
解析
可以发现setState放在异步回调里执行的时候,即使不加await setState,setStaet后面的同步代码也会等到setState执行完后render完和回调完成后才执行。表现跟场景二基本相同。
感觉就像是setState在异步回调里触发时,被强制加上了await变成了同步的,setState后面的同步语句必须要等到setState->render->setStaet callback执行完成后才轮到执行。
场景四:异步回调里调用多次setState
state = {
a:0
}
click = () => {
Promise.resolve().then(res => {
this.setState({
a: 1,
})
console.log(this.state.a)
this.setState({
a: this.state.a + 1,
})
console.log(this.state.a)
})
<!--或-->
setTimeout(() => {
this.setState({
a: 1,
})
console.log(this.state.a)
this.setState({
a: this.state.a + 1,
})
console.log(this.state.a)
})
}
render() {
console.log('render')
<div onClick={this.click}>
点击
</div>
<div className="">{this.state.a}</div>
}
复制代码
执行结果
render //初始化render触发
// 点击按钮后
render
1
render
2
// 页面显示2
复制代码
state = {
a:0
}
click = async () => {
await this.setState({
a: 1,
})
console.log(this.state.a)
this.setState({
a: this.state.a + 1,
})
console.log(this.state.a)
this.setState({
a: this.state.a + 1,
})
console.log(this.state.a)
this.setState({
a: this.state.a + 1,
})
console.log(this.state.a)
}
render() {
console.log('render')
<div onClick={this.click}>
点击
</div>
<div className="">{this.state.a}</div>
}
复制代码
执行结果
render //初始化render触发
// 点击按钮后
render
1
render
2
render
3
render
4
// 页面显示4
复制代码
解析
可见,在async函数里,只要第一次使用了await,以后的setState即使没有使用await关键词,也会同步执行。