Fiber架构是一种软件架构,主要用于构建和运行Web应用程序 。它由Facebook开发,是一种基于JavaScript的框架。Fiber架构的核心思想是通过将应用程序逻辑划分为多个小单元(称为"Fiber"),然后在不同的线程或事件循环中执行这些单元,从而提高应用程序的性能和响应速度。
在Fiber架构中,每个Fiber代表一个工作单元,可能是一个组件、一个函数或者是一个异步任务。Fiber通过一个调度器来管理这些工作单元的执行。调度器会根据优先级和依赖关系来安排Fiber的执行顺序。当一个Fiber执行完成后,它会被放入一个队列中,等待下一次执行。
Fiber架构的优势在于它可以充分利用现代硬件的多核特性,通过在不同的线程或事件循环中执行不同的Fiber,从而提高应用程序的并发性和性能。同时,Fiber架构还提供了更好的错误处理和调试支持,因为它可以轻松地跟踪和调试每个Fiber的执行过程。
任务调度器把大任务拆分到多个task里面完成,来解决dom树太多一次性加载导致的性能问题。
问题:
如何做到每次只渲染少数几个节点?
在下次执行的时候依然从之前的位置执行?
如何控制树的渲染
以上两个问题可以总结为,我们应该如何控制这颗树的渲染?ok, 我们的想法是:
现在分Task来进行,当一个Task渲染完了,如果还有空闲时间,则会继续下一个;这种一次进行处理的结构可以采用链表结构 。所以现在首先需要做的是,如何将这颗树转化为一个链表结构? 要将这颗树转化为一个链表结构,首先需要知道遍历规则,先遍历谁,接下来又是谁,所以首先需要构建遍历规则。
我们采用"一边构建链表关系,一边渲染dom"
如何控制树的渲染
构建遍历规则
遍历规则从根节点开始,从左到右,每次只构建当前节点和它的孩子节点之间关系
遍历顺序如下:
从root节点开始,找它的子节点,然后是兄弟节点;如果没有兄弟节点就找叔叔节点,知道既没有兄弟节点,也没有叔叔节点就表示遍历完成,形成了一个链表;
结合任务调度器实现执行逻辑
javascript
/**
* 1. 创建dom
* 2. 处理props
* 3. 转换链表,设置指针
* 4. 返回下一个要执行的指针
*/
// 记录下一次任务
let nextWorkOFUnit = false
function workLoop(dleDeadline){
// 状态 - 表示是否有空闲时间
let shouldYield = false
// nextWorkOFUnit 有任务的时候才去执行
while(!shouldYield && nextWorkOFUnit){
nextWorkOFUnit = preformWorkOfUnit(nextWorkOFUnit)
shouldYield = dleDeadline.timeRemaining() < 1
}
requestIdleCallback(workLoop)
}
requestIdleCallback(workLoop)
//实现preformWorkOfUnit
function preformWorkOfUnit(work){
// 1. 创建dom
// 如果 work 上本身就有了dom,那么就无需去创建处理
if(!work.dom){
const dom = (work.dom = work.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(work.type))
// 需要将当前的dom 挂载到父节点上
work.parent.dom.append(dom)
// 2.处理props
Object.keys(work.props).forEach(key => {
if (key !== "children") {
dom[key] = el.props[key]
}
})
}
// 3. 转换链表,设置指针
const childrens = work.props.children
let prevChild = null
childrens.forEach((child,index)=>{
// 因为有挂载 child 和 sibling 那么虚拟dom必然没有这两个属性,不能去破坏虚拟dom的结构
const newWork = {
type: child.type,
props: child.props,
child: null,
parent: work,
sibling: null,
dom: null
}
// 先取第一个子节点 挂载到当前执行任务的子节点上
if(index === 0){
work.child = newWork
}else{
// 否则就是兄弟节点
prevChild.sibling = newWork
}
prevChild = newWork
})
// 4. 返回下一个要执行的指针
if(work.child){
return work.child
}
// 然后检查是否有兄弟节点
if(work.sibling) {
return work.sibling
}
// 如果没有,那么就找到parent ,然后去找叔叔节点
return work.parent?.sibling
}