diff
step1: 遍历新children,复用 oldFiber
React 在一次更新中, 当children是一个数组的话,会调用reconcileChildrenArray来调和子代 fiber。
function reconcileChildrenArray(){
/* 第一步 */
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) { // 遍历完newChild后,oldChild还有剩余
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(returnFiber,oldFiber,newChildren[newIdx],expirationTime,); // 匹配复用老 fiber
if (newFiber === null) { break }
// ..一些其他逻辑
}
if (shouldTrackSideEffects) { // shouldTrackSideEffects 为更新流程。
if (oldFiber && newFiber.alternate === null) {
/* 找到了与新节点对应的fiber,但是不能复用,那么直接删除老节点 */
deleteChild(returnFiber, oldFiber);
}
}
}
}
- 使用sibling指向同级兄弟节点。所以在遍历children 遍历,sibling 指针同时移动,找到与 child 对应的 oldFiber 。
- updateSlot 内部会判断当前的 tag 和 key 是否匹配,如果匹配复用老 fiber 形成新的 fiber ,如果不匹配,返回 null 。
step2: 统一删除oldfiber
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
step3: 统一创建newFiber
if(oldFiber === null){
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber,newChildren[newIdx],expirationTime,)
// ...
}
}
step4:针对发生移动和更复杂的情况
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
// 判断 mapRemainingChildren 中有没有可以复用 oldFiber ,如果有,那么复用,如果没有,新创建一个 newFiber 。
const newFiber = updateFromMap(existingChildren,returnFiber)
/* 从mapRemainingChildren删掉已经复用oldFiber */
}
- mapRemainingChildren返回一个map, 存放剩余的老fiber和对应的key.
- 遍历处理剩下的 Children 。
step5: 删除剩余没有复用的oldFiber
if (shouldTrackSideEffects) { // shouldTrackSideEffects 为更新流程。
/* 移除没有复用到的oldFiber */
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
案例1:节点删除
- oldChild: A B C D
- newChild: A B
newChild中,A B经过step1 遍历完成,经过step2 删除 C D
案例2: 节点增加
- oldChild: A B
- newChild: A B C D
A B 经过 step1遍历后,oldFiber 没有可以复用的了,那么经过step3直接创建 C D。
案例3: 节点位置改变
- oldChild: A B C D
- newChild: A B D C
A B 在第一步被有效复用,step2和step3不符合,通过step4的updateFromMap复用C D。
案例4: 复杂情况(删除 + 新增 + 移动)
- oldChild: A B C D
- newChild: A E D B
A 节点,在step1 被复用;接下来直接到step4,遍历 newChild ,E被创建,D B 从 existingChildren 中被复用;existingChildren 还剩一个 C 在step5 被删除。