目的:将整棵树的render拆分成小任务避免页面卡顿
模拟卡顿:在document.append 后面加一个1个亿的 while counter
workLoop
工作机制类似于一个message queue和一个消费者
- Message queue: 根据vdom树生成的fiber树双向链表
- 消费者:workLoop 这个while循环
javascript
// 对于workLoop来讲,即将执行的任务(即将处理的vdom节点)
let nextWorkOfUnit = null;
function workLoop(deadline) {
let shouldYield = false;
while (!shouldYield && nextWorkOfUnit) {
nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
function render(el, container) {
// 将vdom的根节点作为children,创建到 root container 上
// 这是第一个入队的任务
nextWorkOfUnit = {
dom: container, // root container dom是dom树的根节点
props: {
children: [el],
},
};
}
实现Fiber 架构
在render vdom的同时,按照双向链表结构记录当前的work节点和下一个节点
注意: fiber树仅仅是一个逻辑概念,在实现代码时,只会按照逻辑顺序逐个处理每一个节点。并不会保存一个fiber树的数据。而dom树本身就是一个树状结构。
处理一个vdom节点的核心代码逻辑
javascript
function performWorkOfUnit(fiber) {
// 将vdom节点转换为dom节点
// 如果vdom节点对应的dom节点未被创建,先创建dom节点,并append到父节点的dom上
if (!fiber.dom) {
const dom = (fiber.dom = createDom(fiber.type));
fiber.parent.dom.append(dom);
updateProps(dom, fiber.props);
}
// 将vdom的children转换为fiber节点,并链接sibling关系
initChildren(fiber)
console.log(fiber);
// 4. 返回下一个要执行的任务
if (fiber.child) {
return fiber.child;
}
if (fiber.sibling) {
return fiber.sibling;
}
return fiber.parent?.sibling;
}
- 在react v16.9.0 源代码中,关于child,sibling,parent 的处理逻辑在
/packages/react-reconciler/src/ReactFiberWorkLoop.js
中的completeUnitOfWork
函数中。在源码中寻找构建父子兄弟节点关系的部分并不容易,但是通过源码的代码注释我们可以找到相关逻辑:
javascript
function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
workInProgress = unitOfWork;
do {
...
// v16 源码中,parent实际命名为 return
const returnFiber = workInProgress.return;