• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

react基础2

武飞扬头像
青桔啊山竹
帮助1

魔幻的key

key在vue和react中都有用到,都是用来diff的,那么key到底在diff中起到了一个什么样的作用呢?

以下是vue官网中对于key的解释。

Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染

所以如果列表渲染中,每个列表项的dom都不大一样或者非常简单的时候,不用key可以减少dom的重建,但是如果列表项中包含子组件,子组件又依赖父组件,不添加key的情况,很容易造成列表变化没有引起diff重新渲染。

react diff 过程

react中的diff又叫reconcile.即协调

对不同的节点有不同的协调过程:

单节点

 function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    lanes: Lanes,
  ): Fiber {
    const key = element.key;
    let child = currentFirstChild;
    // 从左到右的遍历
    while (child !== null) {
      // TODO: If key === null and child.key === null, then this only applies to
      // the first item in the list.
      if (child.key === key) {
       // switch case 
      } else {
        deleteChild(returnFiber, child);
      }
      // 下一个节点 
      child = child.sibling;
    }

    // 创建新Fiber,并返回

数组

 function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<any>,
    lanes: Lanes,
  ): Fiber | null {
  
    let resultingFirstChild: Fiber | null = null;
    let previousNewFiber: Fiber | null = null;

    let oldFiber = currentFirstChild;
    let lastPlacedIndex = 0;
    let newIdx = 0;
    let nextOldFiber = null;
    // old和new均没有到达尾部的时候
    for (; oldFiber !== null && newIdx < newChildren.length; newIdx  ) {
      if (oldFiber.index > newIdx) {
        nextOldFiber = oldFiber; // 如果是当前的older > newArray,则直接断开链表,没必要继续比较了。
        oldFiber = null;
      } else {
        nextOldFiber = oldFiber.sibling;
      }
      // 更新fiber, updateSlot 在key不相等或不存在的时候会返回null,否则会返回一个fiber
      const newFiber = updateSlot(
        returnFiber,
        oldFiber,
        newChildren[newIdx],
        lanes,
      );
      // 无可复用的fibber,break循环
      if (newFiber === null) {
        if (oldFiber === null) {
          oldFiber = nextOldFiber;
        }
        break;
      }
      // 如果有sideEffect
      if (shouldTrackSideEffects) {
      // 旧的子元素 Fiber 链表中是否存在当前位置的 Fiber 对象,
      // 同时新的 Fiber 对象的 `alternate` 属性是否为 `null`。
      // 如果这两个条件都满足,说明当前位置的 Fiber 对象没有被重用,需要将其从 Fiber 树中删除。
        if (oldFiber && newFiber.alternate === null) {
          deleteChild(returnFiber, oldFiber);
        }
      }
      // 记录比较的位置
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
      // 新链表的头指针记录,只有第一个元素会走到这
        resultingFirstChild = newFiber;
      } else {
        // 新链表的头指针,指向下一个位置,新增
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
      oldFiber = nextOldFiber;
    }

    if (newIdx === newChildren.length) {
      // newArray已经走完了,删除fiber中剩余的元素
      deleteRemainingChildren(returnFiber, oldFiber);
      if (getIsHydrating()) {
        const numberOfForks = newIdx;
        pushTreeFork(returnFiber, numberOfForks);
      }
      return resultingFirstChild;
    }

    if (oldFiber === null) {
    // 如果oldFiber没走,则需要新增newArray中的数据到previousNewFiber
      
      for (; newIdx < newChildren.length; newIdx  ) {
        const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
        if (newFiber === null) {
          continue;
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          // TODO: Move out of the loop. This only happens for the first run.
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
      if (getIsHydrating()) {
        const numberOfForks = newIdx;
        pushTreeFork(returnFiber, numberOfForks);
      }
      return resultingFirstChild;
    }

    // Add all children to a key map for quick lookups.
    // newArray 和OldFiber都没走完,将fiber变成key-fiber的一个map
    const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

    // Keep scanning and use the map to restore deleted items as moves.
    for (; newIdx < newChildren.length; newIdx  ) {
      const newFiber = updateFromMap(
        existingChildren,
        returnFiber,
        newIdx,
        newChildren[newIdx],
        lanes,
      );
      if (newFiber !== null) {
        if (shouldTrackSideEffects) {
          if (newFiber.alternate !== null) {
            // The new fiber is a work in progress, but if there exists a
            // current, that means that we reused the fiber. We need to delete
            // it from the child list so that we don't add it to the deletion
            // list.
            existingChildren.delete(
              newFiber.key === null ? newIdx : newFiber.key,
            );
          }
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
    }

    if (shouldTrackSideEffects) {
      // Any existing children that weren't consumed above were deleted. We need
      // to add them to the deletion list.
      existingChildren.forEach(child => deleteChild(returnFiber, child));
    }

    if (getIsHydrating()) {
      const numberOfForks = newIdx;
      pushTreeFork(returnFiber, numberOfForks);
    }
    return resultingFirstChild;
  }

从上面的源码可以看出,在diff的过程中,发生了两次遍历,第一次是将newArray和oldFiber进行比较,在比较的过程中同时会生成一个newFiber:

  1. 如果currentFiber和currentArrayItem相等,则复用fiber并将链表和数组的指针各向后移动一位,并且将fiber挂载新的resultingFirstChild上。
  2. 如果不相等,没有可复用的fiber,则直接跳出循环
  3. 如果newArray走完了,则说明有需oldFiber中有需要删除的节点,剩余节点全部删除
  4. 如果oldFiber走完了,说明newArray的剩余节点需要增加,把newArray的节点挂载到resultingFirstChild上。
  5. 两个都没走完则开始进行diff最精髓的比较:继续遍历剩下的数据,同样的,每次只比较一个newArray中的数据和一个oldFiber链表上的数据,只是通过用key来获取currentFiber,剩下的逻辑与reconcile的逻辑类似。

比较

相同点:

没有key会默认使用index作为key,则发生元素删除活添加的时候,会重新渲染整个列表,而不是只渲染删除或者增加的元素

不同点:

react中的链表是单链表,有一个指针指向兄弟节点,一个指针指向父亲节点,diff的过程只能单向遍历。

vue中的链表是双向链表,所以可以进行首尾遍历,进行优化。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgahkba
系列文章
更多 icon
同类精品
更多 icon
继续加载