1、出现的问题
上节的递归调用会出现一个问题,也就是一旦开始渲染,就不能停止了,直到渲染出完整的树结构。也就是说会造成主线程被持续占⽤,造成的后果就是主线程上的布局、动画等周期性任务就⽆法立即得到处理,造成视觉上的卡顿,影响⽤户体验。
在浏览器中,页面是一帧一帧绘制出来的,主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次,在这一帧中浏览器要完成JS脚本执行、样式布局、样式绘制,如果在某个阶段执行时间很长,超过 16.6ms,那么就会阻塞页面的渲染,从而出现卡顿现象,也就是常说的掉帧!。
2、如何解决
使用增量渲染(把渲染任务拆分成块,匀到多帧),将把工作分解成小单元,在完成每个单元之后,如果还有其他任务需要完成,我们将让浏览器中断渲染,也就是经常听到的fiber。
关键点:
- 增量渲染
- 更新时能够暂停,终止,复⽤渲染任务
- 给不同类型的更新赋予优先级
3、什么是fiber
用一张非常经典的图:
简单说一下:fiber是指组件上将要完成或者已经完成的任务,每个组件可以⼀个或者多个。
4、window.requestIdleCallback()
实现fiber的核心是window.requestIdleCallback(),window.requestIdleCallback()⽅法将在浏览器的空闲时段内调⽤的函数队列。关于window.requestIdleCallback()请点击查看文档。
你可以把 requestedlecallback 想象成一个 setTimeout,但是浏览器不会告诉你它什么时候运行,而是在主线程空闲时运行回调。
window.requestIdleCallback将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。
我们使用window.requestIdleCallback来实现代码:
// 下一个功能单元
let nextUnitOfWork = null
/**
* 工作循环
* @param {*} deadline 截止时间
*/
function workLoop(deadline) {
// 停止循环标识
let shouldYield = false
// 循环条件为存在下一个工作单元,且没有更高优先级的工作
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(
nextUnitOfWork
)
// 当前帧空余时间要没了,停止工作循环
shouldYield = deadline.timeRemaining() < 1
}
// 空闲时间应该任务
requestIdleCallback(workLoop)
}
// 空闲时间执行任务
requestIdleCallback(workLoop)
// 执行单元事件,返回下一个单元事件
function performUnitOfWork(fiber) {
// TODO
}
performUnitOfWork函数功能我们会在下一节实现。
5、本节代码
代码地址:https://github.com/linhexs/mini-react/tree/3.concurrent
<br /> <br /> <br />