写给自己的react 2 schedular

109 阅读3分钟

上篇说到了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 需要立刻执行。

  • UserBlocking 250ms 超时时间250ms,一般指的是用户交互。

  • Normal 5000ms 超时时间5s,不需要直观立即变化的任务,比如网络请求。

  • Low 10000ms 超时时间10s,肯定要执行的任务,但是可以放在最后处理。

  • Idle 一些没有必要的任务,可能不会执行。

所以,react中的异步更新任务,其实就是通过类似requestIdelCallback去向浏览器做一帧一帧请求,等到浏览器有空闲时间的时候,去执行异步更新任务,保证了页面的流畅。

那么 整个scheduler调度过程是怎么样的呢?

1、第一步将我们的任务插入到队列中等待调度执行
2、第二步取出work任务开始进行调度
3、第三步调度完成之后调用perform开始执行协调渲染

未完待续