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 组件的状态更新时机和机制。