面试官:“React 中 setState 是同步的还是异步?”
我:“异步”
面试官:“有没有可能存在同步的情况?”
我:“……”
setState 是什么?
在 React 中,setState 是类组件中用于更新组件状态的核心 API。当你调用 setState 时,它会请求 React 重新渲染组件,并更新 UI。
参数
setState(nextState, callback?)
-
nextState:一个对象或者函数。- 如果你传递一个对象作为
nextState,它将浅层合并到this.state中。 - 如果你传递一个函数作为
nextState,它将被视为 更新函数。它必须是个纯函数,应该以已加载的 state 和 props 作为参数,并且应该返回要浅层合并到this.state中的对象。React 会将你的更新函数放入队列中并重新渲染你的组件。在下一次渲染期间,React 将通过应用队列中的所有的更新函数来计算下一个 state。
- 如果你传递一个对象作为
-
可选的
callback:如果你指定该函数,React 将在提交更新后调用你提供的callback。
想必各位大佬都用地很熟练了,就不过多赘述。
批处理更新机制
有时候,大家可能会遇到一个常见问题:多次调用 setState 并不会依次生效,最后只会应用最后一次的 setState 结果。原因在于 React 的异步批量更新机制。
class App extends React.Component {
state = {
count: 1000,
};
handleClick = () => {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div className="App">
<button onClick={this.handleClick}>+1</button>
<p>count:{this.state.count}</p>
</div>
);
}
}
上述代码不会导致 count 增加两次,而是仅更新一次。这是因为 setState 的调用是批量处理的,React 合并了两次更新,最终只执行了一次。
每次 setState 调用会触发一次重新渲染,因此它是一个影响性能的操作。为了优化渲染过程,React 引入了异步的批处理更新机制,确保多次状态更新可以合并为一次渲染。
React 的批量更新机制通过一种叫做 “事务机制(transactional batching)” 的方式进行管理。当你调用 setState 时,React 会将这些状态更新操作放入一个队列,等事件处理结束后再进行一次批量更新。这就避免了每次 setState 调用都触发重渲染的问题。
setState 到底是同步还是异步?
这个问题得分情况:如果在 React18 之前你可以说 既是异步又是同步;但是在 React18 ,setState 被归为异步操作。
React 18 之前
在 React 18 之前,我们常说合成事件处理函数或生命周期方法通常是异步的,因为在这个版本中 setState 会在这些场景中进行批量化处理,将这些状态更新操作放入一个队列,等事件处理结束后再进行一次批量更新,这就导致为什么是异步的。
而当你在 React 18 之前的原生 DOM 事件或者setTimeout 中调用 setState 时,setState 是同步的。React 不会进行批量更新,因此状态会立即更新。
这就是为什么React 18 之前我们说setState既是异步又是同步。
React 18 之后
一句话来说就是 —— 任何情况都会自动执行批处理,多次更新始终合并为一次。
举个例子🌰:
// React 17
componentDidMount() {
document.getElementById('btn').addEventListener('click', () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 1001
});
}
// React 18
componentDidMount() {
document.getElementById('btn').addEventListener('click', () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 1000
});
}
- 在 React 17 中,
setState会在原生事件处理程序中同步更新,因此你会立即看到更新后的状态值。 - 在 React 18 中,即使在原生事件处理程序中,
setState也会被批量处理,因此状态更新不再是同步的,而是异步的。
setState 同步做法
在 React18 之后如果想实现同步的效果,即调 setState 后立马拿到最新数据,官方很贴心考虑到这一点,在 React18 新提供了一个API —— flushSync
官方原话是这样讲的:
将
setState视为 请求 而会不是立即更新组件的命令。当多个组件更新它们的 state 来响应事件时,React 将批量更新它们,并在这次事件结束时将它们一并重新渲染。在极少数情况下,你需要强制同步应用特定的 state 更新,这时你可以将其包装在flushSync中,但这可能会损害性能。
用例:
import { flushSync } from 'react-dom'
// 这里相当于一次批处理,flushSync回调执行完后立马执行一次render函数
flushSync(() => {
this.setState({
text: 'Hello,React 18'
})
});
console.log(this.state.text); // 'Hello,React 18'
总结
React 18 后 setState 是异步的
在 React 18 后,setState 被设计为在所有上下文中都是异步的,包括合成事件、原生事件、Promise、setTimeout 等。这是 React 的一种优化机制,用于支持更复杂的并发渲染模式(Concurrent Mode)和提升性能。
所以,如果你真的面试时被问到这个问题,你就说:
- 在 React 18 之前,
setState的行为可能会根据场景表现为同步或异步。 - 然而在 React 18 中,
setState统一变成了异步的,通过批处理优化渲染性能。
参考
最后
码字不易,感谢三连!
已将学习代码上传至 github,欢迎大家学习指正!
技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!