面试官问了一道关于setState的问题,如果在函数调用另外一个函数,这两个函数都会有setSta te,那么这两个setState会合并吗? 我之前在看的一片博客,是这么说的,当我们进入到一个函数的时候isBatchingUpdate设置为true,退出函数的时候isBatchingUpdate设置false,触发一次更新,按照这个逻辑,应该是触发两次更新,但是从面试官的神色中,我察觉到了不对劲,今天用代码测了一下,发现只会触发一次更新,让我对setState的有了全新的理解。
1.为什么setState采用异步更新?
一种常见的说法是为了优化,通过异步的操作方式,累计更新后批量合并处理,减少渲染次数,提升性能,那同步就不能批量合并吗?这显然不能作为setState设计成异步的理由,为什么setState是异步的?
这个问题得到了官方团队的回复,原因有两个:
- 第一个是保持内部的一致性,如果改为同步更新的模式,尽管setState使state变成了同步,但是props不是。
- 第二个是为后续的架构升级启用并发更新,为了完成异步渲染,react会在setState时根据数据来源,设置不同的优先级,这些数据来源可能有事件、回调、句柄、动画效果等,再根据优先级并发处理提升性能,从react17角度分析,异步的设计无疑是正确的,使异步渲染能够在react中落地。
2.什么情况下setState采用异步更新?
那么什么情况下他是同步的呢? 异步场景中的案例使我们建立了这样一个认知,setState是异步的,如果我们将setState放到setTimeOut事件中,那情况就完全不同了。
因为setState并不是真正的异步函数,他实际上使用过队列延迟执行操作来模拟实现的,通过判断isBatchingUpdate来判断setState是先存进state队列,还是去更新,true则执行异步操作 , false 则直接同步更新。
在React自己的生命周期事件和合成事件中可拿到isBatchingUpdate的控制权,将状态放进队列 ,控制执行节奏。在onClick和onFocus的事件中,由于合成事件封装了一层,所以可以将isBa tchingUpdate的状态更新为true,在react生命周期函数同样可以把isBatchingUpdate的状态更新为true。
而在外部的原生事件中,并没有外层的封装和拦截,无法更新isBatchingUpdate的状态true ,这就会造成isBatchingUpdate的状态只会为false,且一直执行,所以在addEventListen-er、setTimeOut、setInterval这些原生事件中都会同步更新。
3.react的合成事件?
合成事件的原理是事件委托,React给doucument挂上事件监听,DOM事件触发后冒泡到docu ment,React找到一个对应的组件,生成一个合成事件,并按照组件数模拟一遍事件冒泡。
但是这就会造成一个问题,页面中只能有一个React根节点,如果有多个React根节点,那么事件就乱套了,值得一提的是这个问题在React17中得到了解决,事件委托不再挂在doucument上,而是挂在到dom.render作用的节点上。