这是我参与更文挑战的第15天,活动详情查看: 更文挑战
背景
我们知道React的setState方法并不是同步执行的。 在React的生命周期中发生的多次setState的变更会进行合并,最终减少推送给浏览器的DOM变更次数,从而提升前端性能。那么这部分到底是怎么实现的呢?
- 事件处理函数自带batchedUpdates
- setTimeout中没有batchedUpdates
- 主动batchedUpdates
第1种:
批量更新,会打印 0 0 0,然后按钮文本显示为1。每次 setState 虽然都会经过 enqueueUpdate (创建update 并加入队列)-> scheduleWork(寻找对应的 FiberRoot 节点)-> requestWork (把 FiberRoot 加入到调度队列),可惜上下文变量 isBatchingUpdates 在外部某个地方被标记为了 true ,因此本次 setState 一路走来,尚未到达接下来的 performSyncWork 或者 scheduleCakkbackWithExpirationTime 就开始一路 return 出栈。
isBatchingUpdates 变量在早前的调用栈中(我们为 onClick 绑定的事件处理函数会被 react 包裹多层),被标记为了 true ,然后 fn(a, b) 内部经过了3次 setState 系列操作,然后 finally 中 isBatchingUpdates 恢复为之前的 false,此时执行同步更新工作 performSyncWork 。
第2种:
在 add 中使用 setTimeout 将 this.countNumber 包裹了一层 setTimeout(() => { this.countNumber()}, 0) ,同样要调用 add 也是先经过 interactiveUpdates1 来说继续把自己的 performSyncWork 执行完,就算结束了。显然不管 performSyncWork 做了什么同步更新,我们的 setState 目前为止都还没得到执行。然后等到 setTimeout 的回调函数等到空闲被执行的时候,才会执行 setState ,此时没有了批量更新之上下文,所以每个 setState 都会单独执行一遍 requestWork 中的 performSyncWork 直到渲染结束,且不会被打断,3次 setState 就会整个更新渲染 3 遍(这样性能不好,所以一般不会这样写 react)。
什么叫不会被打断的同步更新渲染?看一下 demo 中的输出,每次都同步打印出了最新的 button dom 的 innerText 。
第3种:
已经可以猜到,无非就是因为使用 setTimeout 而“错过了”第一次的批量更新上下文,那等到 setTimeout 的回调执行的时候,专门再创建一个批量更新上下文即可。
function App() {
const [a, setA] = useState(0);
function add() {
setTimeout(() => {
console.log('run 4');
setA(a + 3);
console.log('run 5');
setA(a + 8);
console.log('run 6');
});
console.log('run 1');
setA(a + 1);
console.log('run 2');
setA(a + 1);
console.log('run 3');
}
console.log('trigger');
return (
<Fragment>
<div onClick={add}>{a}</div>
</Fragment>
)
}