同步异步 & 批处理
TIP
属于 React 控制之内的会走异步批处理,控制之外的会走同步非批处理。
- 在原生事件或异步代码中,
setState是同步,非批处理。 - 在合成事件或钩子函数中,
setState是异步,批处理。
注 1:对于 setState,类组件中无论前后值是否一致,都会导致重渲染;函数组件中若前后值一致,理论上不会导致重渲染(但仍可能重渲染,不过不会重渲染子组件)。
注 2:React 本身执行的过程和代码都是同步的,只是合成事件和生命周期钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,就是我们所说的异步了。
WARNING
React 18 引入了新特性 Automatic Batching 自动批处理,该特性使得在原生事件或异步代码中也会自动走批处理。后续待研究。
同步 & 非批处理
原生事件
class App extends Component {
state = {
count: 0
}
handleClick = () => {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 3
}
componentDidMount() {
document.body.addEventListener('click', this.handleClick)
}
render() {
return <div>{this.state.count}</div> // 3
}
}
异步代码
class App extends Component {
state = {
count: 0
}
handleClick = () => {
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 3
}, 0)
}
render() {
return <div onClick={this.handleClick}>{this.state.count}</div> // 3
}
}
合成事件绑定的函数中嵌套异步代码,异步代码中的 setState 采用同步非批处理,遵循就近原则。
异步 & 批处理
合成事件
class App extends Component {
state = {
count: 0
}
handleClick = () => {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 0
}
render() {
return <div onClick={this.handleClick}>{this.state.count}</div> // 1
}
}
钩子函数
class App extends Component {
state = {
count: 0
}
componentDidMount() {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 0
}
render() {
return <div>{this.state.count}</div> // 1
}
}
TIP
在合成事件和钩子函数中,setState 是异步,批处理。
但有个例外,setState 参数是函数形式时,虽其仍是异步,但不参与批处理。
class App extends Component {
state = {
count: 0
}
handleClick = () => {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState(state => {
return { count: state.count + 1 }
})
console.log(this.state.count) // 0
}
render() {
return <div onClick={this.handleClick}>{this.state.count}</div> // 2
}
}
思考题
class Example extends React.Component {
constructor() {
super()
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val); // 第 1 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val); // 第 3 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null
}
}
// React 18 之前输出
0、0、2、3
// React 18 之后输出。待研究。
0、0、1、1