1. 程式人生 > 其它 >react進階第八講:key

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