react进阶第八讲:key


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 被删除。