JavaScript 是单线程语言,它通过事件循环来处理异步操作。事件循环的主要概念包括两个队列:
setTimeout
、setInterval
、I/O
操作等。Promise
的回调、MutationObserver
等。事件循环的工作原理是:在当前执行栈中的任务执行完毕后,先处理所有的微任务队列,然后再处理宏任务队列中的第一个任务。
React 通常会将多个 setState
调用批量处理,以减少重新渲染的次数,这种批量处理通常在同一个事件循环中完成。React 会在事件循环结束前,通过 micro task
或其他机制执行合并后的更新操作。
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); // 输出 1
}, 1000);
}
render() {
return <div>{this.state.count}</div>;
}
}
在这个例子中:
setTimeout
的回调函数被放入宏任务队列。setState
被调用,React 会立即更新状态,因为此时没有其他任务需要批量处理。console.log(this.state.count)
输出的是更新后的状态值。在 React 18 中,React 引入了自动批量处理机制,这使得无论是同步任务还是异步任务中的 setState
,React 都会将多个 setState
调用合并在一起进行处理。因此,尽管在 setTimeout
中 setState
的表现看似是同步的,但实际上仍然是经过批量处理的。
在 setTimeout
中,setState
看似同步执行的原因在于:
setTimeout
回调中的代码会在所有微任务处理完后执行。setState
在 setTimeout
中被调用时,由于当前没有其他的批量更新任务,React 立即更新状态。理解这一点有助于在开发中更好地掌控 React 组件的状态更新时机和机制。