想象一下在某个组件中增加一个计数器:
class SomeComponent extends Component{
state = {
updatedByDiv: '',
updatedByBtn: '',
counter: 0
}
divCountHandler = () => {
this.setState({
updatedByDiv: 'Div',
counter: this.state.counter + 1
});
console.log('divCountHandler executed');
}
btnCountHandler = () => {
this.setState({
updatedByBtn: 'Button',
counter: this.state.counter + 1
});
console.log('btnCountHandler executed');
}
...
...
render(){
return (
...
// a parent div
<div onClick={this.divCountHandler}>
// a child button
<button onClick={this.btnCountHandler}>Increment Count</button>
</div>
...
)
}
}
父组件和子组件都有一个计数处理程序。这是有意完成的,因此我们可以在同一个点击事件冒泡上下文中执行两次 setState(),但在 2 个不同的处理程序中。
正如我们想象的那样,按钮上的单击事件现在将触发这两个处理程序,因为事件在冒泡阶段从目标冒泡到最外层的容器。
因此 btnCountHandler() 先执行,预期将计数增加到 1,然后 divCountHandler() 执行,预期将计数增加到 2。
但是,您可以在 React Developer 工具中检查,计数只会增加到 1。
这证明反应了
对所有 setState 调用进行排队
在执行完上下文中的最后一个方法(本例中为 divCountHandler)后返回此队列
将发生在同一上下文中的多个 setState 调用中的所有对象突变(例如,单个事件阶段内的所有方法调用都是相同的上下文)合并到一个单一的对象突变语法中(合并是有意义的,因为这就是为什么我们可以首先在 setState() 中独立更新状态属性)
并将其传递给一个单独的 setState() 以防止由于多次 setState() 调用而重新渲染(这是对批处理的非常原始的描述)。
react 运行的结果代码:
this.setState({
updatedByDiv: 'Div',
updatedByBtn: 'Button',
counter: this.state.counter + 1
})
为了阻止这种行为,不是将对象作为参数传递给 setState 方法,而是传递回调。
divCountHandler = () => {
this.setState((prevState, props) => {
return {
updatedByDiv: 'Div',
counter: prevState.counter + 1
};
});
console.log('divCountHandler executed');
}
btnCountHandler = () => {
this.setState((prevState, props) => {
return {
updatedByBtn: 'Button',
counter: prevState.counter + 1
};
});
console.log('btnCountHandler executed');
}
最后一个方法执行完成后,当 react 返回处理 setState 队列时,它只是为每个排队的 setState 调用回调,并传入之前的组件状态。
这种方式 react 确保队列中的最后一个回调能够更新其所有先前对应物已处理的状态。