一个例子:
import React from "react";
import "./styles.css";
export default class App extends React.Component{
state = {
count: 0
}
increment = () => {
console.log('increment setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
console.log('increment setState后的count', this.state.count)
}
triple = () => {
console.log('triple setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
console.log('triple setState后的count', this.state.count)
}
reduce = () => {
setTimeout(() => {
console.log('reduce setState前的count', this.state.count)
this.setState({
count: this.state.count - 1
});
console.log('reduce setState后的count', this.state.count)
},0);
}
render(){
return <div>
<button onClick={this.increment}>点我增加</button>
<button onClick={this.triple}>点我增加三倍</button>
<button onClick={this.reduce}>点我减少</button>
</div>
}
}
一次点击 点我增加、点我增加三倍、点我减少,打印输出为:
increment setState前的count 0
increment setState后的count 0
triple setState前的count 1
triple setState后的count 1
reduce setState前的count 2
reduce setState后的count 1
前面的5个打印没有太多疑问:setState是异步的操作,但是最后一个,表现上是同步的操作
这里要从setState的批量更新说起: 每一次setState,都会触发一次update的生命周期:setState --> shouldComponentUpdate --> componentWillUpdate --> render --> componentDidUpdate
一个完整的更新流程,涉及了包括 re-render(重渲染) 在内的多个步骤。re-render 本身涉及对 DOM 的操作,它会带来较大的性能开销。假如说“一次 setState 就触发一个完整的更新流程”这个结论成立,那么每一次 setState 的调用都会触发一次 re-render,我们的视图很可能没刷新几次就卡死了
因此,这正是 setState 异步的一个重要的动机——避免频繁的 re-render
在实际的 React 运行时中,setState 异步的实现方式有点类似于 Vue 的 $nextTick 和浏览器里的 Event-Loop:每来一个 setState,就把它塞进一个队列里“攒起来”。等时机成熟,再把“攒起来”的 state 结果做合并,最后只针对最新的 state 值走一次更新流程。这个过程,叫作“批量更新”
实现上:React有一个全局的变量isBatchingUpdates,isBatchingUpdates 这个变量,在 React 的生命周期函数以及合成事件执行前会被修改为true,这时我们所做的 setState 操作自然不会立即生效,添加进队列中当函数执行完毕后,会再把 isBatchingUpdates 改为 false,而在setTimeout内部执行时,isBatchingUpdates已经被修改为了false所以造成了同步的假象。
对整个 setState 工作流做一个总结 setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同:在 React 钩子函数及合成事件中,它表现为异步;而在 setTimeout、setInterval 等函数中,包括在 DOM 原生事件中,它都表现为同步。这种差异,本质上是由 React 事务机制和批量更新机制的工作方式来决定的。