在 setTimeout中,setState 看似同步执行的原因,主要与 React 的更新机制和 JavaScript 的事件循环有关。让我们深入探讨一下其中的原理。
1.
JavaScript 事件循环与任务队列
JavaScript 是单线程语言,它通过事件循环来处理异步操作。事件循环的主要概念包括两个队列:
宏任务队列(macro task queue)
:包括
setTimeout
、
setInterval
、
I/O
操作等。
微任务队列(micro task queue)
:包括
Promise
的回调、
MutationObserver
等。
事件循环的工作原理是:在当前执行栈中的任务执行完毕后,先处理所有的微任务队列,然后再处理宏任务队列中的第一个任务。
2.
React 的批量更新机制
React 通常会将多个
setState
调用批量处理,以减少重新渲染的次数,这种批量处理通常在同一个事件循环中完成。React 会在事件循环结束前,通过
micro task
或其他机制执行合并后的更新操作。
3.
setTimeout
与
setState
的同步执行
当你在
setTimeout
的回调函数中调用
setState
时,事情是这样发生的:
当
setTimeout
到期后,它的回调被放入宏任务队列。
事件循环处理完当前所有的微任务后,会执行宏任务队列中的
setTimeout
回调。
在这个
setTimeout
回调函数执行时,
setState
被调用。由于这个时候已经没有其他的任务需要执行,React 会立即处理状态更新,而不是像在事件处理函数中那样将它放入更新队列中。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
componentDidMount() {
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
}, 1000);
render() {
return <div>{this.state.count}</div>;
在这个例子中:
setTimeout
的回调函数被放入宏任务队列。
当事件循环处理到这个宏任务时,它会执行回调函数。
setState
被调用,React 会立即更新状态,因为此时没有其他任务需要批量处理。
因此,console.log(this.state.count)
输出的是更新后的状态值。
4. React 18 中的改进
在 React 18 中,React 引入了自动批量处理机制,这使得无论是同步任务还是异步任务中的 setState
,React 都会将多个 setState
调用合并在一起进行处理。因此,尽管在 setTimeout
中 setState
的表现看似是同步的,但实际上仍然是经过批量处理的。
在 setTimeout
中,setState
看似同步执行的原因在于:
JavaScript 的事件循环机制决定了 setTimeout
回调中的代码会在所有微任务处理完后执行。
当 setState
在 setTimeout
中被调用时,由于当前没有其他的批量更新任务,React 立即更新状态。