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

  • 相关阅读:
    4g项目shell脚本
    shell脚本执行方法
    linux 4g项目定时启动脚本
    java面试-mysql优化
    java面试-java8特性
    java面试-oom内存溢出有几种类型
    java面试-动态代理
    java面试-ThreadLocal
    java面试-类加载过程
    java面试-JVM内存模型
  • 原文地址:https://www.cnblogs.com/renzhiwei2017/p/15670833.html
Copyright © 2020-2023  润新知