本
文
摘
要
作者| 慕课网精英讲师 北瑶
本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!
setState不能只单纯地说它是同步的,也有异步的。这里所说的同步和异步指的是 API 调用后更新 DOM 是同步还是异步的,而同步和异步主要取决于它被调用的环境。要搞清楚这个问题,还是得搞懂setState 原理。在面试场景中,只要和 React 相关,面试官一定舔着脸问你:“ 宝子,setState 是同步还是异步的呀 ” 。面对这样的 *** 刁难,我们需要先明确,从 API 层面上说,它就是普通的调用执行的函数,自然是同步 API 。因此,这里所说的同步和异步指的是 API 调用后更新 DOM 是同步还是异步的。来,上代码
通过结果我们发现,非常奇怪的一个现象:第一次事件执行显然为异步的,先打印了两个 0,Dom 随之改变为 1 ;第二次同样是异步的,但是我们发现多次执行没效果 (异步?);而第三次又是同步执行的了;先说结论,首先,同步和异步主要取决于它被调用的环境。如果 setState 在 React 能够控制的范围被调用,它就是异步的。 比如合成事件处理函数, 生命周期函数, 此时会进行批量更新, 也就是将状态合并后再进行 DOM 更新。如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。比如原生事件处理函数中, 定时器回调函数中, Ajax 回调函数中, 此时 setState 被调用后会立即更新 DOM 。为什么会这样呢?其实,我们看到的所谓的 “异步”,是开启了 “批量更新” 模式的。批量更新模式可以减少真实 DOM 渲染的次数,所以只要是 React 能够控制的范围,出于性能因素考虑,一定是批量更新模式。批量更新会先合并状态,再一次性做 DOM 更新。那么假设没有批量更新呢?从生命周期的角度来看,每一次的 setState 都是一个完整的更新流程,这里面就包含了重新渲染 (re-render) 在内的很多操作,大体的流程如下:shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate;re-render 本身涉及对 DOM 的操作,它会带来较大的性能开销。假如说 “一次 setState 就触发一个完整的更新流程” 这个结论成立,那么每一次 setState的调用都会触发一次 re-render,我们的视图很可能没刷新几次就卡死了,渲染就会出现下面这样的流程:
因此,setState 异步(或者说是批量更新)的一个重要动机就是避免频繁的 re-render。在实际的 React 运行时中,setState 异步的实现方式有点类似于浏览器里的 Event-Loop:每来一个setState,就把它塞进一个队列里。等时机成熟,再把队列里的 state 结果做合并,最后只针对最新的 state 值走一次更新流程。这个过程,叫作“批量更新”,批量更新的过程正如下面代码中的箭头流程图所示:
只要我们的同步代码还在执行,“进队列” 这个动作就不会停止。因此就算我们在React 中写了一个 N 次的 setState 循环,也只是会增加 state 任务入队的次数,并不会带来频繁的 re-render。当 N 次调用结束后,仅仅是 state 的任务队列内容发生了变化, state 本身并不会立刻改变。我帮你梳理了 setState 的执行流程图:
当然,你可能看不懂这个流程图(是有多笨啊),没关系,下面还会有的。如果为非批量更新模式,调用多少次 setState 就会渲染多少次真实 DOM,性能较低。但是我们在某些条件下需要对 JS 控制的区域实现批量更新 ( 异步更新 DOM ) ,那应该怎么做呢?强制批量更新其实很简单,我都不好意思说 so easy ,因为这玩意简直就是 so TM 的 easy 。我们只需要将代码包裹在 unstable_batchedUpdates 方法的回调函数中就可以实现强制批量更新。具体使用方式也很简单,从 react-dom 中引入进来,然后将代码放入调用函数中就可以了。
欢迎关注「慕课网」,发现更多IT圈优质内容,分享干货知识,帮助你成为更好的程序员!