React 中的 setState 是微任务还是宏任务
首先这个问法就表明 setState 是一个异步的任务
- 和 setTimeout 以及 Promise.then 做比较
首先在 React 的事件处理函数中 & React 的生命周期中 的 setState 是不会立即执行的,然后我们就发现 setState 执行完毕的时间是早于 Promise.then 的,但是这并不能证明他就是一个同步任务
handleClick = () => {
const fans = Math.floor(Math.random() * 10)
setTimeout(() => {
console.log('宏任务触发')
})
Promise.resolve().then(() => {
console.log('微任务触发')
})
this.setState({
count: this.state.count + fans
}, () => {
console.log('新增粉丝数:', fans)
})
}
- 和同步的 console.log 做对比
handleClick = () => {
const fans = Math.floor(Math.random() * 10)
console.log('开始运行')
this.setState({
count: this.state.count + fans
}, () => {
console.log('新增粉丝数:', fans)
})
console.log('结束运行')
}
结果
开始运行
结束运行
新增粉丝数: xxx
这么看,似乎 setState 又是一个异步的操作。主要原因是,在 React 的生命周期以及绑定的事件流中,所有的 setState 操作会先缓存到一个队列中,在整个事件结束后或者 mount 流程结束后,才会取出之前缓存的 setState 队列进行一次计算,触发 state 更新。只要我们跳出 React 的事件流或者生命周期,就能打破 React 对 setState 的掌控。最简单的方法,就是把 setState 放到 setTimeout 的匿名函数中。
- 将 setState 放在定时器中
handleClick = () => {
setTimeout(() => {
const fans = Math.floor(Math.random() * 10)
console.log('开始运行')
this.setState({
count: this.state.count + fans
}, () => {
console.log('新增粉丝数:', fans)
})
console.log('结束运行')
})
}
结果:
开始运行
新增粉丝数:xxx
结束运行
所以,setState 就是一次同步行为.
React 是怎么控制 setState 的
React 在绑定事件时,会对事件进行合成,统一绑定到 document 上( react@17 有所改变,变成了绑定事件到 render 时指定的那个 DOM 元素),最后由 React 来派发。
所有的事件在触发的时候,都会先调用 batchedEventUpdates$1 这个方法,在这里就会修改 executionContext 的值,React 就知道此时的 setState 在自己的掌控中。
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (lane === SyncLane) {
// 同步操作
ensureRootIsScheduled(root, eventTime);
// 判断当前是否还在 React 事件流中
// 如果不在,直接调用 flushSyncCallbackQueue 更新
if (executionContext === NoContext) {
flushSyncCallbackQueue();
}
} else {
// 异步操作
}
}
判断了 executionContext 是否等于 NoContext 来确定当前更新流程是否在 React 事件流中。
所以,不管是直接调用 flushSyncCallbackQueue ,还是推迟调用,这里本质上都是同步的,只是有个先后顺序的问题。
Component.setState 方法最终会调用 enqueueSetState 方法,而 enqueueSetState 方法内部会调用 scheduleUpdateOnFiber 方法,区别就在于正常调用的时候,scheduleUpdateOnFiber 方法内只会调用 ensureRootIsScheduled ,在事件方法结束后,才会调用 flushSyncCallbackQueue 方法。而脱离 React 事件流的时候,scheduleUpdateOnFiber 在 ensureRootIsScheduled 调用结束后,会直接调用 flushSyncCallbackQueue 方法,这个方法就是用来更新 state 并重新进行 render
结论: setState 是同步的