欢迎关注react源码系列一起学习源码呀。Github常更新,掘金不常更新。Github仓库中不仅有webpack生态源码,react源码,还有前端基础知识,八股文,踩坑经验等
setState同步更新还是异步更新?
- React17稳定版本或者React17以前的版本中,即legacy模式下,在react能够接管的地方,比如生命周期或者合成事件中,setState是异步更新的。 但是在setTimeout或者通过window.addEventListener添加的原生事件中,setState则是同步的。
// Legacy同步模式
const container = document.getElementById('root');
ReactDOM.render(<App />, container);
- 在React开发版本中,即concurrent模式下,setState的更新统一是异步的
// Concurrent异步模式,在这个模式下,任何情况下setState都是异步更新的。目前createRoot方法还在实验中
const container = document.getElementById('root');
// ReactDOM.render(<App />, container);
ReactDOM.createRoot(container).render(<App />)
setState更新场景
以下面的代码为例,以下示例均在react@17.0.1,react-dom@17.0.1版本的legacy模式下实现
import React from 'react';
class App extends React.Component {
constructor(props){
super(props);
this.state = {
number: 0
}
}
handleClick = event => {
// todo
}
render(){
console.log('render...', this.state)
return (
<div>
计数器:{this.state.number}
<div>
<button onClick={this.handleClick}>add</button>
</div>
</div>
)
}
}
export default App;
setState异步更新场景
handleClick = event => {
this.setState({ number: this.state.number + 1 }, () => {
console.log('setState1 callback', this.state)
})
console.log('after setState1', this.state) // number: 0
this.setState({ number: this.state.number + 1 }, () => {
console.log('setState2 callback', this.state)
})
console.log('after setState2', this.state) // number: 0
}
点击按钮,打印顺序:
after setState1 {number: 0}
after setState2 {number: 0}
render... {number: 1}
setState1 callback {number: 1}
setState2 callback {number: 1}
可以得出以下结论:
- 状态是异步更新的
- setState的回调函数是在状态更新后批量执行的
setState同步更新场景:setTimeout中
handleClick = event => {
setTimeout(() => {
this.setState({ number: this.state.number + 1 }, () => {
console.log('setState1 callback', this.state)
})
console.log('after setState1', this.state) // number: 0
this.setState({ number: this.state.number + 1 }, () => {
console.log('setState2 callback', this.state)
})
console.log('after setState2', this.state) // number: 0
}, 4)
}
打印顺序:
render... {number: 1}
setState1 callback {number: 1}
after setState1 {number: 1}
render... {number: 2}
setState2 callback {number: 2}
after setState2 {number: 2}
可以得出以下结论
- 状态是同步更新的,因此setState的回调也是同步执行的
注意观察render的打印时机以及次数!!!!
setState参数是函数的场景
handleClick = event => {
this.setState((prevState) => {
console.log('setState1...', prevState)
return { number: prevState.number + 1 }
}, () => {
console.log('setState1 callback', this.state)
})
console.log('after setState1', this.state)
this.setState((prevState) => {
console.log('setState2...', prevState)
return { number: prevState.number + 1 }
}, () => {
console.log('setState2 callback', this.state)
})
console.log('after setState2', this.state)
}
打印顺序:
after setState1 {number: 0}
after setState2 {number: 0}
setState1... {number: 0}
setState2... {number: 1}
render... {number: 2}
setState1 callback {number: 2}
setState2 callback {number: 2}
结论:
- 状态是异步更新的
- 可以发现在批量输出
setState1...
setState2...
后,即刻打印render...
,最后打印setState1 callback
以及setState2 callback
,这也正是说明setState的回调函数是在render更新之后执行的
setState接收函数的场景:setTimeout
handleClick = event => {
setTimeout(() => {
this.setState((prevState) => {
console.log('setState1...', prevState)
return { number: prevState.number + 1 }
}, () => {
console.log('setState1 callback', this.state)
})
console.log('after setState1', this.state)
this.setState((prevState) => {
console.log('setState2...', prevState)
return { number: prevState.number + 1 }
}, () => {
console.log('setState2 callback', this.state)
})
console.log('after setState2', this.state)
}, 4);
}
打印顺序:
setState1... {number: 0}
render... {number: 1}
setState1 callback {number: 1}
after setState1 {number: 1}
setState2... {number: 1}
render... {number: 2}
setState2 callback {number: 2}
after setState2 {number: 2}
结论
- 状态是同步更新的
- 一定要仔细品味setState、render、 setState callback、after setState打印顺序之间的关系!!!!
ReactDOM.unstable_batchedUpdates强制异步更新的场景
handleClick = event => {
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
this.setState((prevState) => {
console.log('setState1...', prevState)
return { number: prevState.number + 1 }
}, () => {
console.log('setState1 callback', this.state)
})
console.log('after setState1', this.state)
this.setState((prevState) => {
console.log('setState2...', prevState)
return { number: prevState.number + 1 }
}, () => {
console.log('setState2 callback', this.state)
})
console.log('after setState2', this.state)
})
}, 4);
}
打印顺序:
after setState1 {number: 0}
after setState2 {number: 0}
setState1... {number: 0}
setState2... {number: 1}
render... {number: 2}
setState1 callback {number: 2}
setState2 callback {number: 2}
结论:
- 在同步模式(legacy)下,如果需要在setTimeout等中启用异步更新,可以使用React17新增的
ReactDOM.unstable_batchedUpdates
API
原理
如果需要了解setState同步异步更新的原理,请看下一篇文章