「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」
今天来填昨天的坑,来好好研究一下 reconcileChildrenArray,它有什么锤子用,怎么实现的?
函数作用
它的函数签名如下:
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {}
函数是用于渲染组件的子元素列表的。returnFiber 需要和 newChildren 做 diff,然后返回重建的 Fiber。
需要用到的其他函数
- updateSlot:更新 fiber,最终都是调用 createFiber 创建一个新的 fiber 以表示更新后的结果fiber 并返回
- deleteRemainingChildren:删除剩余的孩子 fiber
- mapRemainingChildren: 返回一个 map,给剩余的孩子 fiber 建立一个 key map,即键为 fiber的 key 属性(不存在则使用 index 充当 key),值为 fiber
- createChild:根据 newChild 的值创建新的 Fiber
- placeChild:更新 newFiber 在新的后代 fiber 列表中的位置 index,同时根据 index 与 lastPlacedIndex 的前后关系决定是否标记 newFiber.flags 是否加上 Placement
- deleteChild:将被删的孩子 fiber 加入到 returnFibers.deletions 数组中,returnFibers.flags 加上 ChildDeletion,以便执行 useEffect、useLayoutEffect 等副作用钩子的清除函数
- getIsHydrating:检查 isHydrating 是否为 true
- pushTreeFork:将孩子fiber 列表的父 fiber 和孩子 fiber 数入栈
代码拆解
代码可以拆成下面几个部分:
- 变量准备
- 第一个 for 循环
- 两个 for 循环代码块之间的操作
- 第二个 for 循环
- 第二个 for 循环之后的操作
变量准备
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
resultingFirstChild 用作返回值,表示与 newChildren 同步后的后代 fiber 的第一个 fiber。
previousNewFiber 表示上一个新建的 Fiber,用于将当前新建的 Fiber 链到 previousNewFiber 的 sibling 域,以长子-兄弟链表表示法构建基于 newChildren、以 returnFiber 为根的新 Fiber 树。
oldFiber 表示正在处理的旧 Fiber。
lastPlacedIndex 表示当前已经新建的 Fiber 的 index 的最大值,为 placeChild 函数服务。
newIdx 表示遍历 newChildren 的索引指针
nextOldFiber 表示 oldFiber 的下一个右紧邻兄弟 fiber。
第一个 for 循环
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
遍历 newChildren,对于 oldFiber.index 小于等于 newIdx 的,执行 updateSlot 得到新 fiber,如果需要跟踪副作用,判断新旧 fiber 的 diff结果是否为删除,如果是则通过 deleteChild 将被删 fiber 加到 returnFiber 的 deletions 中;调用 placeChild 确定 newFiber 的 index 并条件性添加 Placement flag。循环中新建的 fiber 会通过 sibling 域连成一个 sibling 链表。
两个 for 循环之间的代码
如果跳出循环的时候,newChildren的元素已经被遍历完了,则将旧 fiber 树剩余的孩子 fiber “删除”(对剩余的旧 fiber 全部调 deleteChild`),返回新 fiber 树的长子,即 resultingFirstChild。
反之,如果跳出第一个 for 循环的时候,newChildren 还有元素没有遍历完,这些元素都是要待插入的新 fiber,执行第二个 for 循环。
第二个 for 循环
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
说白了,就是为多出来的 newChildren 建立一个个 newFiber(通过 createChild 创建),依次尾插到前面建立的新 fiber 树的 sibling 链表中,最终reconcileChildrenArray返回的是 newChildren 对应的新 fiber 树的“长子”,即 resultingFirstChild。