react diff算法

  • 给新增,移动,和删除节点设置fiber.falgs(新增, 移动: Placement, 删除: Deletion)
  • 如果是需要删除的fiber, 除了自身打上Deletion之外, 还要将其添加到父节点的effects链表中(正常副作用队列的处理是在completeWork函数, 但是该节点(被删除)会脱离fiber树, 不会再进入completeWork阶段, 所以在beginWork阶段提前加入副作用队列).

时间复杂度:

  • 从上至下比较整个树形结构, 时间复杂度被缩短到 O(n)

基本比较:

  • fiber对象与ReactElement对象相比较,并不是两棵 fiber 树相比较, 而是旧fiber对象与新ReactElement对象向比较, 结果生成新的fiber子节点

比较方案

  • 单节点比较、多节点数组类型比较

reconcileChildFibers

function ChildReconciler(shouldTrackSideEffects) {

  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
    // Handle object types
    // 判断是单节点比较还是多节点比较
    const isObject = typeof newChild === 'object' && newChild !== null;

    if (isObject) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          // newChild是单节点
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
      }
    }
    //  newChild是多节点
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }
    // ...
  }

  return reconcileChildFibers;
}

单节点比较

reconcileSingleElement

  • 未匹配到key和type,新建节点,并给当前节点打上Deletion标记
  • 匹配到key和type,会复用dom节点,即新fiber对象.stateNode = currentFirstChild.stateNode
// 只保留主杆逻辑
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  const key = element.key;
  let child = currentFirstChild;

  while (child !== null) {
    // currentFirstChild !== null, 表明是对比更新阶段
    if (child.key === key) {
      // 1. key相同, 进一步判断 child.elementType === element.type
      switch (child.tag) {
        // 只看核心逻辑
        default: {
          if (child.elementType === element.type) {
            // 1.1 已经匹配上了, 如果有兄弟节点, 需要给兄弟节点打上Deletion标记
            deleteRemainingChildren(returnFiber, child.sibling);
            // 1.2 构造fiber节点, 新的fiber对象会复用current.stateNode, 即可复用DOM对象
            const existing = useFiber(child, element.props);
            existing.ref = coerceRef(returnFiber, child, element);
            existing.return = returnFiber;
            return existing;
          }
          break;
        }
      }
      // Didn't match. 给当前节点打上Deletion标记
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      // 2. key不相同, 匹配失败, 给当前节点打上Deletion标记
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }
  
  //...
  
  // 新建节点,未匹配上
  const created = createFiberFromElement(element, returnFiber.mode, lanes);
  created.ref = coerceRef(returnFiber, currentFirstChild, element);
  created.return = returnFiber;
  return created;
}

createChild

  function createChild(
        returnFiber: Fiber,
        newChild: any,
        lanes: Lanes,
      ): Fiber | null {
        if (typeof newChild === 'object' && newChild !== null) {
          switch (newChild.$$typeof) {
            case REACT_ELEMENT_TYPE: {
              // 调用构造函数进行创建
              const created = createFiberFromElement(
                newChild,
                returnFiber.mode,
                lanes,
              );
              return created;
            }
          }
        }

        return null;
      }

多节点比较

  • reconcileChildrenArray实际就是 2 个序列之间的比较(链表oldFiber和数组newChildren), 最后返回尽量复用之前的fiber的序列.
  • 遍历最长公共序列(key 相同), 公共序列的节点都视为可复用
    • 如果newChildren序列被遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)
    • 如果oldFiber序列被遍历完, 那么newChildren序列中剩余节点都视为新增(打上Placement标记)
  • 遍历剩余非公共序列, 优先复用 oldFiber 序列中的节点
    在这里插入图片描述

第二次循环, 会遍历剩余序列E,C,X,Y

  • 生成的 fiber 节点fiber(E), fiber©可以复用. 其中fiber©节点发生了位移(打上Placement标记).
  • fiber(X), fiber(Y)是新增(打上Placement标记).
  • 同时oldFiber序列中的fiber(D)节点确定被删除(打上Deletion标记).
    在这里插入图片描述
function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  lanes: Lanes,
): Fiber | null {
 	
 	if (__DEV__) {
      // First, validate keys.
      let knownKeys = null;
      for (let i = 0; i < newChildren.length; i++) {
        const child = newChildren[i];
        // 1. 在dev环境下, 执行warnOnInvalidKey. 
        //  - 如果没有设置key, 会警告提示, 希望能显示设置key
        //  - 如果key重复, 会错误提示.
        knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);
      }
    }
  
  //比较完成最终的fiber链表结构
  let resultingFirstChild: Fiber | null = null;
  let previousNewFiber: Fiber | null = null;

  let oldFiber = currentFirstChild;
  //需要移动的fiber位置
  let lastPlacedIndex = 0;
  let newIdx = 0;
  let nextOldFiber = null;
  
  // 1. 第一次循环: 遍历最长公共序列(key相同), 公共序列的节点都视为可复用
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
  	  //移动指针,获取下一个oldFiber
      if (oldFiber.index > newIdx) {
	    nextOldFiber = oldFiber;
	    oldFiber = null;
	  } else {
	    nextOldFiber = oldFiber.sibling;
	  }
	  // new槽位和old槽位进行比较, 如果key不同, 返回null
	  // key相同, 比较type是否一致. type一致则执行useFiber(update逻辑), type不一致则运行createXXX(insert逻辑)
	  const newFiber = updateSlot(
	    returnFiber,
	    oldFiber,
	    newChildren[newIdx],
	    lanes,
	  );
	
	  if (newFiber === null) {
	    // 如果返回null, 表明key不同. 无法满足公共序列条件, 退出循环
	    if (oldFiber === null) {
	      oldFiber = nextOldFiber;
	    }
	    break;
	  }
	  if (shouldTrackSideEffects) {
	    // 若是新增节点, 则给老节点打上Deletion标记
	    if (oldFiber && newFiber.alternate === null) {
	      deleteChild(returnFiber, oldFiber);
	    }
	  }
	
	  // lastPlacedIndex 记录被移动的节点索引
	  // 如果当前节点可复用, 则要判断位置是否移动.
	  lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
	
	  // 更新resultingFirstChild结果序列
	  if (previousNewFiber === null) {
	    resultingFirstChild = newFiber;
	  } else {
	    previousNewFiber.sibling = newFiber;
	  }
	  //移动旧fiber指针
	  previousNewFiber = newFiber;
	  oldFiber = nextOldFiber;

  }
  
  //第一次公共节点遍历完毕
  if (newIdx === newChildren.length) {
    // 如果newChildren序列被遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)
    deleteRemainingChildren(returnFiber, oldFiber);
    return resultingFirstChild;
  }
  
  //第一次公共节点遍历完毕
  if (oldFiber === null) {
    // 如果oldFiber序列被遍历完, 那么newChildren序列中剩余节点都视为新增(打上Placement标记)
    for (; newIdx < newChildren.length; newIdx++) {
      // 
    }
    return resultingFirstChild;
  }

  // ==================分割线==================
  
  //将第一次循环后, oldFiber剩余序列加入到一个map中. 目的是为了第二次循环能顺利的找到可复用节点
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

  // 2. 第二次循环: 遍历剩余非公共序列, 优先复用oldFiber序列中的节点
  for (; newIdx < newChildren.length; newIdx++) {
	  const newFiber = updateFromMap(
	    existingChildren,
	    returnFiber,
	    newIdx,
	    newChildren[newIdx],
	    lanes,
	  );
	  if (newFiber !== null) {
	    if (shouldTrackSideEffects) {
	      if (newFiber.alternate !== null) {
	        // 如果newFiber是通过复用创建的, 则清理map中对应的老节点
	        existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);
	      }
	    }
	    // 更新移动fiber索引
	    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
	    // 更新resultingFirstChild结果序列
	    if (previousNewFiber === null) {
	      resultingFirstChild = newFiber;
	    } else {
	      previousNewFiber.sibling = newFiber;
	    }
	    previousNewFiber = newFiber;
	  }
  }

  if (shouldTrackSideEffects) {
    // newChildren已经遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)
    existingChildren.forEach(child => deleteChild(returnFiber, child));
  }

  return resultingFirstChild;
}

updateSlot

    function updateSlot(
      returnFiber: Fiber,
      oldFiber: Fiber | null,
      newChild: any,
      lanes: Lanes,
    ): Fiber | null {
      const key = oldFiber !== null ? oldFiber.key : null;

      if (typeof newChild === 'object' && newChild !== null) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE: {
            //key用于是否复用的第一判断条件
            if (newChild.key === key) {
              return updateElement(returnFiber, oldFiber, newChild, lanes);
            } else {
              return null;
            }
          }
        }
      }

      return null;
    }

updateFromMap

    function updateFromMap(
      existingChildren: Map<string | number, Fiber>,
      returnFiber: Fiber,
      newIdx: number,
      newChild: any,
      lanes: Lanes,
    ): Fiber | null {

      if (typeof newChild === 'object' && newChild !== null) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE: {
            //key用于是否复用的第一判断条件
            //查找复用旧fiber节点
            const matchedFiber =
              existingChildren.get(
                //如果没有key就会用索引来当作key
                newChild.key === null ? newIdx : newChild.key,
              ) || null;
            return updateElement(returnFiber, matchedFiber, newChild, lanes);
          }
      }
      return null;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值