时间分片 2
上一篇我们首先提出了三个问题:
1. 为什么需要时间分片?
2. 时间分片是怎么实现的?
3. 所谓的时间分片它"分"的到底是啥?
我们简单回顾一下~
首先 1. 为什么需要时间分片: 因为react15之前的版本中,当dom树开始进行更新之后,直到所有的节点都被遍历完成之后才会停下来。在这段时间之内,任何交互或者动画都是执行不了的。如果节点特别多,假设遍历一边要花费10s中,那么页面就会直接卡顿10s,包括页面上的动画等。所以react16企图通过时间分片,来减少这种情况下的卡顿时间(注意只是单纯减少卡顿时间而已,改卡还是得卡,至于为啥还会卡呢,一会后头说)。
其次 2. 时间分片是怎么实现的: 我们之所以能看到页面上的动画在动,是因为浏览器在不停地刷新页面。由于刷新页面的间隔时间非常短,所以从视觉上我们觉得元素像是连贯,流畅的运动(实际每一帧是静态的)。浏览器的刷新频率大概是每隔33ms刷新一次(不绝对是)。在这33ms内,浏览器主要完成几件事,第一件是执行业务代码中的js代码,第二件是执行requestAnimationFrame,第三件是重新计算页面元素的位置并刷新页面(这一步完事儿之后,就可以看到页面上的变化了)。这几步一般是在一帧内完成。当然也可能无法在一帧内完成,比如第一步中的js代码执行时间特别长的话,那么这一帧就会响应变长,同时页面也会感觉到卡顿。然后当完成上面那三步之后,如果前三步一共花费的时间,还没有到一帧的总时长(假设一帧总共33ms,假设前三步花费20ms,那么这一帧还剩下13ms),那么这个时候我们这一帧就还剩下一些空闲的时间,于是有些浏览器就提供了一个叫做requestIdleCallback的函数,这个函数中的代码,会被浏览器放到这所谓的空余时间内执行。在空余时间之内执行的代码,是不会影响页面的动画以及交互的。react16的时间分片,就是把react中的复杂运算,比如diff算法等放在这个阶段。
以上是我们上一篇中说的内容,接下来我们来讨论一下第三个问题,也就是:
3. 时间分片,分的到底是啥?
首先我们先记住一点: react的底层源码在执行的时候,从宏观角度来看一共分为两大阶段:
1. render阶段
2. commit阶段
我们就简单介绍一下这俩步骤都干了什么,至于具体的源码分析放到后面的笔记中。
首先render阶段: 主要就是用来生成新的fiber树并diff出有变化的节点。
然后commit阶段: 获取到render阶段中diff出来的发生了变化的节点的fiber,通过原生api更新页面
也就是说,在render阶段中,只会做一些复杂的运算,并不会真正的操作页面,直到commit阶段才会去通过js的原生api去修改dom。
那么我们上一篇所说的时间分片,分的到底是哪个部分呢?是render阶段?还是commit阶段?还是两个阶段都分呢?
其实我们之前已经说了,时间分片分的是复杂运算的过程,所以其实分的,就只有render阶段而已。
或者大家可以换一个角度想想,如果我们在commit阶段也进行时间分片的话,那么会发生什么事情?
假设我们有30个div需要更新,我们通过js原生api更新,也就是说这个更新的api需要被调用30次,若是我们采用时间分片的方式更新的话,假设时间分片把30个div分成三次更新,那么每次只能更新10个div,当三次以后30个div才更新完毕。
如果是以上这种情况,我们想想页面会变成什么样?大家应该都体验过,当网速慢的时候加载图片,图片会一点点的才被渲染出来。诶对,我们这30个div就会变成跟网速慢时加载图片一样,一点点的才被刷新出来。这样显然是不符合我们的用户体验的,所以react选择了只对render阶段进行时间分片,而commit阶段,也就是真正操作dom更新dom的这个阶段,是完全同步的一个过程~