上篇说到了react16的架构,分别是Scheduler,Reconciler,Renderer。我们还是根据setData来继续深入。
现在,产品提出一个需求,要你将一组任务依次执行,请估点数。
1分钟,先搞个队列,将任务放进队列中,依照先进先出的原则进行任务的调度。
很好,那么此时再加一个需求,任务都有优先级,根据优先级进行调度。
那还不简单,根据优先级的字段,进行判断来进行调度。
现在思路是正确的,但是如果任务又臭又长,我们需要一直执行知道任务全部完成吗?
需要知道的是,即使GUI线程和js线程都存在渲染进程中,但是由于这两个线程互斥,只能同一时间执行某一个,如果javascript执行时间太长,会影响到浏览器的渲染。
所以在react中,使用了时间分片的方式,当浏览器有空闲时间,才会执行这些任务
那么,时间切片是什么呢?
时间切片:
浏览器在一帧中,也就是16ms内需要执行以下操作:
处理事件->执行js->调用requestAnimation->布局Layout->绘制Paint,如果没有其他事件,那么浏览器就会进入空闲时间。
react就是利用空闲时间来执行任务,那么如何知道浏览器的空闲时间呢?
谷歌浏览器提供了一个接口,requestIdleCallback
requestIdleCallback(callback,{ timeout })
- callback 回调,浏览器空余时间执行回调函数。
- timeout 超时时间。如果浏览器长时间没有空闲,那么回调就不会执行,为了解决这个问题,可以通过 requestIdleCallback 的第二个参数指定一个超时时间。
我们可以在callback里加上task,期望在浏览器空闲时执行
但是,这个api只有谷歌浏览器有,为了兼容各个浏览器,那么react就需要模拟requestIdelCallback。
那要怎么模拟呢?我们知道,在js事件循环中,分别有宏任务和微任务,而宏任务是在下一次事件循环中执行,并不会阻塞上一次浏览器更新,而且浏览器一次只会执行一次宏任务,但是因为宏任务递归执行时,会有4ms的浪费,对于1秒60帧,一帧16ms的执行来说,4ms确实是一种浪费。于是,react使用了Message Channel。
但是,为了防止task因为浏览器没有空闲时间而卡死,react又对每个task设置了优先级
-
Immediate-1 需要立刻执行。 -
UserBlocking250ms 超时时间250ms,一般指的是用户交互。 -
Normal5000ms 超时时间5s,不需要直观立即变化的任务,比如网络请求。 -
Low10000ms 超时时间10s,肯定要执行的任务,但是可以放在最后处理。 -
Idle一些没有必要的任务,可能不会执行。
所以,react中的异步更新任务,其实就是通过类似requestIdelCallback去向浏览器做一帧一帧请求,等到浏览器有空闲时间的时候,去执行异步更新任务,保证了页面的流畅。
那么 整个scheduler调度过程是怎么样的呢?
1、第一步将我们的任务插入到队列中等待调度执行
2、第二步取出work任务开始进行调度
3、第三步调度完成之后调用perform开始执行协调渲染
未完待续