时间切片的实现和调度(原创2.6万字) (6)

源码文件

function startProfilerTimer(fiber: Fiber): void { if (!enableProfilerTimer) { return; } profilerStartTime = now(); if (((fiber.actualStartTime: any): number) < 0) { fiber.actualStartTime = now(); } } function stopProfilerTimerIfRunningAndRecordDelta( fiber: Fiber, overrideBaseTime: boolean, ): void { if (!enableProfilerTimer) { return; } if (profilerStartTime >= 0) { const elapsedTime = now() - profilerStartTime; fiber.actualDuration += elapsedTime; if (overrideBaseTime) { fiber.selfBaseDuration = elapsedTime; } profilerStartTime = -1; } }

最后,就到了 beginWork 流程了 - -。里面有什么呢? workInProgress 还有一大堆的 switch case。

想看 beginWork 源码的可以自行尝试 beginWork相关源码文件

总结

最后是总结部分,该不该写这个想了很久,每个读者在不同时间不同心境下看源码的感悟应该是不一样的(当然自己回顾的时候也是读者)。每次看应该都有每个时期的总结。

但是如果不写总结,这篇解析又感觉枯燥无味,且没有结果。所以简单略过一下(肯定是原创啦,别的地方没有的)

fiber其实就是一个节点,是链表的遍历形式

fiber 通过优先级计算 expirationTime 得到过期时间

因为链表结构所以时间切片可以做到很方便的中断和恢复

时间切片的实现是通过 settimeout + postMessage 实现的

当所有任务都延迟时会执行 clearTimeout

任务数 和 工作时间的计算

Fiber 为什么要使用链表

使用链表结构只是一个结果,而不是目的,React 开发者一开始的目的是冲着模拟调用栈去的

调用栈最经常被用于存放子程序的返回地址。在调用任何子程序时,主程序都必须暂存子程序运行完毕后应该返回到的地址。因此,如果被调用的子程序还要调用其他的子程序,其自身的返回地址就必须存入调用栈,在其自身运行完毕后再行取回。除了返回地址,还会保存本地变量、函数参数、环境传递。

因此 Fiber 对象被设计成一个链表结构,通过以下主要属性组成一个链表

type 类型

return 存储当前节点的父节点

child 存储第一个子节点

sibling 存储右边第一个的兄弟节点

alternate 旧树的同等节点

我们在遍历 dom 树 diff 的时候,即使中断了,我们只需要记住中断时候的那么一个节点,就可以在下个时间片恢复继续遍历并 diff。这就是 fiber 数据结构选用链表的一大好处。

时间切片为什么不用 requestIdleCallback

浏览器个周期执行的事件

1. 宏任务 2. 微任务 4. requestAnimationFrame 5. IntersectionObserver 6. 更新界面 7. requestIdleCallback 8. 下一帧

根据官方描述:

window.requestIdleCallback() 方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间 timeout,则有可能为了在超时前执行函数而打乱执行顺序。
你可以在空闲回调函数中调用 requestIdleCallback(),以便在下一次通过事件循环之前调度另一个回调。

看似完美契合时间切片的思想,所以起初 React 的时间分片渲染就想要用到这个 API,不过目前浏览器支持的不给力,而且 requestIdleCallback 有点过于严格,并且执行频率不足以实现流畅的UI呈现。

而且我们希望通过Fiber 架构,让 reconcilation 过程变成可被中断。'适时'地让出 CPU 执行权。因此React团队不得不实现自己的版本。

实际上 Fiber 的思想和协程的概念是契合的。举个栗子:

普通函数: (无法被中断和恢复)

const tasks = [] function run() { let task while (task = tasks.shift()) { execute(task) } }

如果使用 Generator 语法:

const tasks = [] function * run() { let task while (task = tasks.shift()) { // 判断是否有高优先级事件需要处理, 有的话让出控制权 if (hasHighPriorityEvent()) { yield } // 处理完高优先级事件后,恢复函数调用栈,继续执行... execute(task) } }

但是 React 尝试过用 Generator 实现,后来发现很麻烦,就放弃了。

为什么时间切片不使用 Generator

主要是2个原因:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfxjf.html