两种节点类型

咱们能够从同级的节点数量将Diff分为两类:

当newChild类型为object、number、string,代表同级只有一个节点

当newChild类型为Array,同级有多个节点

在接下来两节咱们会别离探讨这两类节点的Diff,留神这里的单节点是指虚构dom节点是个单或者多节点,能够简略看做是不是返回的数组

单节点

单节点比拟还是比较简单的

//删除节点  function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {    if (!shouldTrackSideEffects) {      // Noop.      return;    }//effect链的解决    const last = returnFiber.lastEffect;    if (last !== null) {      last.nextEffect = childToDelete;      returnFiber.lastEffect = childToDelete;    } else {        //证实临时还没有造成链须要第一个节点      returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;    }    const deletions = returnFiber.deletions;    if (deletions === null) {      returnFiber.deletions = [childToDelete];      // TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)      returnFiber.effectTag |= Deletion;    } else {      deletions.push(childToDelete);    }    childToDelete.nextEffect = null;  }//批量删除节点的工具函数(更精确的是批量标记)  function deleteRemainingChildren(    returnFiber: Fiber,    currentFirstChild: Fiber | null,  ): null {    if (!shouldTrackSideEffects) {      // Noop.      return null;    }    // TODO: For the shouldClone case, this could be micro-optimized a bit by    // assuming that after the first child we've already added everything.    let childToDelete = currentFirstChild;    while (childToDelete !== null) {      deleteChild(returnFiber, childToDelete);      childToDelete = childToDelete.sibling;    }    return null;  }//element其实就是新的虚构dom function reconcileSingleElement(  returnFiber: Fiber,  currentFirstChild: Fiber | null,  element: ReactElement): Fiber {  const key = element.key;  let child = currentFirstChild;    // 首先判断是否存在对应DOM节点  while (child !== null) {    // 上一次更新存在DOM节点,接下来判断是否可复用    // 首先比拟key是否雷同    if (child.key === key) {      // key雷同,接下来比拟type是否雷同      switch (child.tag) {        // ...省略case                default: {          if (child.elementType === element.type) {            // type雷同则示意能够复用            deleteRemainingChildren(returnFiber, child.sibling);//显然这个节点的后续节点都必须删除了 因为找到了            const existing = useFiber(child, element.props);//useFiber故名思义 这里的element.props就是后续看是否要调整的属性            // 返回复用的fiber            return existing;          }                    // type不同则跳出switch          break;        }      }      // 代码执行到这里代表:key雷同然而type不同      // 将该fiber及其兄弟fiber标记为删除      deleteRemainingChildren(returnFiber, child);      break;    } else {      // key不同,将该fiber标记为删除      deleteChild(returnFiber, child);    }    child = child.sibling;  }  // 创立新Fiber,并返回 ...省略}

能够发现须要被删除的fiber 不会在这间接真的删除,而是造成一个effect链,另外父节点会保护一个deletions的fiber数组

首先判断child是否存在,不存在则间接开始兄弟节点的比拟,while终止在同层比拟实现后
几种逻辑分支

  1. key雷同,类型也雷同间接可复用,后续就看属性状况更新属性即可
  2. key雷同,类型不同了,间接deleteRemainingChildren 删除这个节点及他的兄弟节点,这里是因为key雷同了,后续没有持续比拟找可复用节点的意义了,故把原节点删完就能够了
  3. key不同,间接把这个比拟的节点删除

多节点

先整顿一下源码

  function reconcileChildrenArray(    returnFiber: Fiber,    currentFirstChild: Fiber | null,    newChildren: Array<*>,    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;    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {      if (oldFiber.index > newIdx) {        nextOldFiber = oldFiber;        oldFiber = null;      } else {        nextOldFiber = oldFiber.sibling;      }      const newFiber = updateSlot(        returnFiber,        oldFiber,        newChildren[newIdx],        lanes,      );      if (newFiber === null) {        if (oldFiber === null) {          oldFiber = nextOldFiber;        }        break;      }      if (shouldTrackSideEffects) {        if (oldFiber && newFiber.alternate === null) {          // We matched the slot, but we didn't reuse the existing fiber, so we          // need to delete the existing child.          deleteChild(returnFiber, oldFiber);        }      }      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);      if (previousNewFiber === null) {        //构建新的fiber链作为返回值        // TODO: Move out of the loop. This only happens for the first run.        resultingFirstChild = newFiber;      } else {        previousNewFiber.sibling = newFiber;      }      previousNewFiber = newFiber;      oldFiber = nextOldFiber;    }    if (newIdx === newChildren.length) {      // We've reached the end of the new children. We can delete the rest.      deleteRemainingChildren(returnFiber, oldFiber);      return resultingFirstChild;    }    if (oldFiber === null) {      // If we don't have any more existing children we can choose a fast path      // since the rest will all be insertions.      for (; newIdx < newChildren.length; newIdx++) {        const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);        if (newFiber === null) {          continue;        }        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);        if (previousNewFiber === null) {//构建新的fiber链作为返回值          // TODO: Move out of the loop. This only happens for the first run.          resultingFirstChild = newFiber;        } else {          previousNewFiber.sibling = newFiber;        }        previousNewFiber = newFiber;      }      return resultingFirstChild;    }    // Add all children to a key map for quick lookups.    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) {            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));    }    return resultingFirstChild;  }

先对几个用到的重要函数解读一下
updateSlot这个函数能够简略了解为节点比拟,如果不匹配返回null,不然就是一个可复用的fiber节点

  function mapRemainingChildren(    returnFiber: Fiber,    currentFirstChild: Fiber,  ): Map<string | number, Fiber> {    const existingChildren: Map<string | number, Fiber> = new Map();    let existingChild = currentFirstChild;    while (existingChild !== null) {      if (existingChild.key !== null) {        existingChildren.set(existingChild.key, existingChild);      } else {        existingChildren.set(existingChild.index, existingChild);      }      existingChild = existingChild.sibling;    }    return existingChildren;  }

mapRemainingChildren返回一个children形成的Map key为id或者是child的index

内容比拟多倡议分成三个步骤去看

  1. 第一个循环,比拟newChildren 和oldFiber和他的兄弟们 会有三种状况

    • key不同循环间接进行
    • key雷同,类型不同,fiber标记为删除循环持续 i++ oldFiber = nextOldFiber;
    • 循环完结newChildren或者是oldFiber和他的兄弟们遍历完结了
  2. 循环完解决一下几种状况 ,一种是newChildren现遍历完了,那删除残余的oldFiber,deleteRemainingChildren(returnFiber, oldFiber); 第二种是oldFiber遍历完了,那残余的newChildren 须要创立fiber节点 并且拼接在previousNewFiber这个后果链上 触发这两种状况都会退出整个diff
  3. 也就是都没有遍历完,状况就是因为节点地位挪动导致的,这个时候先要mapRemainingChildren(returnFiber, oldFiber);把残余的fiber做一个Map映射,而后newChildren 残余的节点去Map中查找,重点是placeChild函数
  function placeChild(    newFiber: Fiber,    lastPlacedIndex: number,    newIndex: number,  ): number {    newFiber.index = newIndex;    if (!shouldTrackSideEffects) {      // Noop.      return lastPlacedIndex;    }    const current = newFiber.alternate;    if (current !== null) {      const oldIndex = current.index;      if (oldIndex < lastPlacedIndex) {        // This is a move.        //本来节点的index比以后最近一次替换过的节点的index还小的话标记为挪动,且lastPlacedIndex不变        newFiber.effectTag = Placement;        return lastPlacedIndex;      } else {        // This item can stay in place.        //返回原有节点的地位作为新的lastPlacedIndex        return oldIndex;      }    } else {      // This is an insertion.        //newChildren在本来没有 齐全是新建的      newFiber.effectTag = Placement;      return lastPlacedIndex;    }  }

举个例子如果 01234要变为12304 假如lastPlacedIndex为0初始开始循环
1 oldIndex为1 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex也就是1

2 oldIndex为2 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex也就是2

3 oldIndex为3 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex也就是3

0 oldIndex为0 oldIndex < lastPlacedIndex 标记挪动 lastPlacedIndex 不变还是3

4 oldIndex为4 oldIndex > lastPlacedIndex 不动 lastPlacedIndex 改为4