1、出现的问题

上节的递归调用会出现一个问题,也就是一旦开始渲染,就不能停止了,直到渲染出完整的树结构。也就是说会造成主线程被持续占⽤,造成的后果就是主线程上的布局、动画等周期性任务就⽆法立即得到处理,造成视觉上的卡顿,影响⽤户体验。

在浏览器中,页面是一帧一帧绘制出来的,主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次,在这一帧中浏览器要完成JS脚本执行、样式布局、样式绘制,如果在某个阶段执行时间很长,超过 16.6ms,那么就会阻塞页面的渲染,从而出现卡顿现象,也就是常说的掉帧!。

2、如何解决

使用增量渲染(把渲染任务拆分成块,匀到多帧),将把工作分解成小单元,在完成每个单元之后,如果还有其他任务需要完成,我们将让浏览器中断渲染,也就是经常听到的fiber。

关键点:

  • 增量渲染
  • 更新时能够暂停,终止,复⽤渲染任务
  • 给不同类型的更新赋予优先级

3、什么是fiber

用一张非常经典的图:
image.png
简单说一下:fiber是指组件上将要完成或者已经完成的任务,每个组件可以⼀个或者多个。

4、window.requestIdleCallback()

实现fiber的核心是window.requestIdleCallback()window.requestIdleCallback()⽅法将在浏览器的空闲时段内调⽤的函数队列。关于window.requestIdleCallback()请点击查看文档。

你可以把 requestedlecallback 想象成一个 setTimeout,但是浏览器不会告诉你它什么时候运行,而是在主线程空闲时运行回调。

window.requestIdleCallback将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。

我们使用window.requestIdleCallback来实现代码:

  1. // 下一个功能单元
  2. let nextUnitOfWork = null
  3. /**
  4. * 工作循环
  5. * @param {*} deadline 截止时间
  6. */
  7. function workLoop(deadline) {
  8. // 停止循环标识
  9. let shouldYield = false
  10. // 循环条件为存在下一个工作单元,且没有更高优先级的工作
  11. while (nextUnitOfWork && !shouldYield) {
  12. nextUnitOfWork = performUnitOfWork(
  13. nextUnitOfWork
  14. )
  15. // 当前帧空余时间要没了,停止工作循环
  16. shouldYield = deadline.timeRemaining() < 1
  17. }
  18. // 空闲时间应该任务
  19. requestIdleCallback(workLoop)
  20. }
  21. // 空闲时间执行任务
  22. requestIdleCallback(workLoop)
  23. // 执行单元事件,返回下一个单元事件
  24. function performUnitOfWork(fiber) {
  25. // TODO
  26. }


performUnitOfWork函数功能我们会在下一节实现。

5、本节代码

代码地址:https://github.com/linhexs/mini-react/tree/3.concurrent











  1. <br /> <br /> <br />