Skip to content
目录

reconcileChildren 调和单个节点

typescript
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  // current === null 说明是第一次渲染
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // 说明渲染过了 现在是更新
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderExpirationTime,
    );
  }
}
typescript
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);

在这里,reconcileChildFibers和mountChildFibers都是调用的ChildReconciler,最后返回的函数都是reconcileChildFibers,ChildReconciler接受的布尔的参数 为 shouldTrackSideEffects(代表是否更新副作用)
在第一次mount的时候是不更新副作用的,只有当后续的更新才更新副作用

reconcileChildFibers

typescript
function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    // 判断是否是一个Fragment组件
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
    // 如果是一个Fragment,直接渲染它的子组件就好了
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // Handle object types
    const isObject = typeof newChild === 'object' && newChild !== null;
    // 如果当前JSXElement是一个对象,说明是一个单独的jsx节点或者portal
    if (isObject) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
        case REACT_PORTAL_TYPE:
          return placeSingleChild(
            reconcileSinglePortal(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
      }
    }
    // 说明是一个文字节点
    if (typeof newChild === 'string' || typeof newChild === 'number') {
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          '' + newChild,
          expirationTime,
        ),
      );
    }

    // 说明是多个节点
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        expirationTime,
      );
    }
    // 是可迭代的节点
    if (getIteratorFn(newChild)) {
      return reconcileChildrenIterator(
        returnFiber,
        currentFirstChild,
        newChild,
        expirationTime,
      );
    }

    // 随后 如果currentFirstChild 是null,说明是更新了,子节点变成了null
    // 就需要将老的子节点都删除
    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

reconcileSingleElement

typescript
function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    expirationTime: ExpirationTime,
  ): Fiber {
    const key = element.key;
    let child = currentFirstChild;
    // 刚进到这里 如果child等于null,说明是第一次渲染
    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) {
        // key相等的情况
        if (
          child.tag === Fragment
            ? element.type === REACT_FRAGMENT_TYPE
            : child.elementType === element.type
        ) {
          // key相等以及elementType都相等
          // 说明当前节点是匹配的,兄弟节点是多余的 将多余的兄弟节点都删除
          deleteRemainingChildren(returnFiber, child.sibling);
          // 复用Fiber
          const existing = useFiber(
            child,
            element.type === REACT_FRAGMENT_TYPE
              ? element.props.children
              : element.props,
            expirationTime,
          );
          existing.ref = coerceRef(returnFiber, child, element);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        } else {
          // key相同但是type不同,因为此时新的节点只有一个
          // 说明都不能复用了,将所有的老的节点都删除
          deleteRemainingChildren(returnFiber, child);
          break;
        }
      } else {
        // 如果key不相等,说明老的child在更新后不存在了,
        // 需要删除,这里只是删除单个节点
        deleteChild(returnFiber, child);
      }
      child = child.sibling;
    }

    if (element.type === REACT_FRAGMENT_TYPE) {
      const created = createFiberFromFragment(
        element.props.children,
        returnFiber.mode,
        expirationTime,
        element.key,
      );
      created.return = returnFiber;
      return created;
    } else {
      const created = createFiberFromElement(
        element,
        returnFiber.mode,
        expirationTime,
      );
      created.ref = coerceRef(returnFiber, currentFirstChild, element);
      created.return = returnFiber;
      return created;
    }
  }

reconcileSingleTextNode

typescript
function reconcileSingleTextNode(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    textContent: string,
    expirationTime: ExpirationTime,
  ): Fiber {
    // 这里只对比老的字节点的第一个是否为text节点 ,如果老的第一个也是text节点
    if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
      // 将除text节点之外的兄弟节点都进行删除
      deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
      // 直接复用当前Fiber节点
      const existing = useFiber(currentFirstChild, textContent, expirationTime);
      existing.return = returnFiber;
      return existing;
    }
    // 如果老的节点的第一个不是text节点,则删除所有的节点,重新创建一个text节点
    deleteRemainingChildren(returnFiber, currentFirstChild);
    const created = createFiberFromText(
      textContent,
      returnFiber.mode,
      expirationTime,
    );
    created.return = returnFiber;
    return created;
  }

reconcileChildrenArray

当新节点为数组的情况

typescript
function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<*>,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    // 最终要返回的第一个Fiber节点,也就是链表结构的头节点
    let resultingFirstChild: Fiber | null = null;
    // 兄弟Fiber节点之间是链表结构,previousNewFiber代表的是链表的最后一个节点
    let previousNewFiber: Fiber | null = null;
    // 老的Fiber
    let oldFiber = currentFirstChild;
    // 最后一个放置的索引 
    let lastPlacedIndex = 0;
    let newIdx = 0;
    let nextOldFiber = null;
    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
      // oldFiber.index > newIdx 说明新老节点的顺序不一致了
      if (oldFiber.index > newIdx) {
        // 不一致的情况下 nextOldFiber 直接等于当前老的Fiber
        nextOldFiber = oldFiber;
        // 老的Fiber不能跟当前的newChildren不能复用了
        oldFiber = null;
      } else {
        // 否则 等于老的Fiber的兄弟节点
        nextOldFiber = oldFiber.sibling;
      }
      // updateSlot 对比新老节点的key是否相同,相同复用Fiber
      // 不相同 不能复用,返回null 如果key相同,但是节点的类型不同,此时是新创建了Fiber的
      const newFiber = updateSlot(
        returnFiber,
        oldFiber,
        newChildren[newIdx],
        expirationTime,
      );
      if (newFiber === null) {
        if (oldFiber === null) {
          oldFiber = nextOldFiber;
        }
        // 到这里跳出循环,找到第一个不能复用的Fiber节点 跳出循环,进行下面的操作
        break;
      }
      if (shouldTrackSideEffects) {
        // 在更新的时候, 如果newFiber.alternate === null 说明当前newFiber是新创建的
        // 并没有复用oldFiber 那就删除老的Fiber
        // 如果Fiber新老Fiber的key相同,但是节点类型不同,此时的newFiber就是新创建的
        // newFiber.alternate 就是等于null
        if (oldFiber && newFiber.alternate === null) {
          deleteChild(returnFiber, oldFiber);
        }
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      // 将newFiber加入到resultingFirstChild的链表上
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
      // oldFiber指向下一个,继续for循环查找第一个key不相同的Fiber节点
      oldFiber = nextOldFiber;
    }

    if (newIdx === newChildren.length) {
      // 新的Fiber已经都创建完了 把剩下的老的节点都删除
      deleteRemainingChildren(returnFiber, oldFiber);
      return resultingFirstChild;
    }

    if (oldFiber === null) {
      // 如果老的fiber不存在 则直接遍历新的child,然后创建newChild的Fiber
      for (; newIdx < newChildren.length; newIdx++) {
        const newFiber = createChild(
          returnFiber,
          newChildren[newIdx],
          expirationTime,
        );
        if (!newFiber) {
          continue;
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
      return resultingFirstChild;
    }
    // 走到这 说明老的Fiber还有,并且新的newChild还没有创建完
    // 将老的Fiber存到一个map上,供下面匹配查找
    const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
    for (; newIdx < newChildren.length; newIdx++) {
      // 从map上,查找能复用的Fiber,没有就新创建
      const newFiber = updateFromMap(
        existingChildren,
        returnFiber,
        newIdx,
        newChildren[newIdx],
        expirationTime,
      );
      if (newFiber) {
        if (shouldTrackSideEffects) {
          if (newFiber.alternate !== null) {
            // 如果newFiber.alternate不等于null,说明是复用的老的Fiber
            // 那么就将map上的Fiber清除掉,因为已经复用了
            existingChildren.delete(
              newFiber.key === null ? newIdx : newFiber.key,
            );
          }
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        // 将当前newFiber添加到链表上
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
    }

    if (shouldTrackSideEffects) {
      // newChild创建完了,如果老的Fiber还有,都删除掉
      existingChildren.forEach(child => deleteChild(returnFiber, child));
    }

    return resultingFirstChild;
  }

总结一下:reconcileChildrenArray分为以下几个步骤

  1. 第一步,先遍历找到第一个不能复用的Fiber
  2. 第二步,如果newChild都创建完了,删除老的Fiber 节点
  3. 第三步,如果老的Fiber节点都复用完了,新的newChild还没创建完,直接遍历创建newChild的Fiber
  4. 第四步,当新老节点都有,将老的节点生成一个map结构,然后根据key或者index去map中查找能否复用
  5. 第五步, 如果newChild的Fiber都创建完了,但是老的Fiber还有,遍历将老的Fiber都删除

updateSlot

typescript
function updateSlot(
    returnFiber: Fiber,
    oldFiber: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    // 获取key
    const key = oldFiber !== null ? oldFiber.key : null;
    // 说明新节点是一个文字节点
    if (typeof newChild === 'string' || typeof newChild === 'number') {
      // key不等于null 说明老的Fiber不是一个文本节点,但是新的节点是文本节点
      // 说明当前Fiber不能复用,直接return null
      if (key !== null) {
        return null;
      }
      // key等于null,说明之前也是一个文本节点,直接复用文本节点的Fiber
      return updateTextNode(
        returnFiber,
        oldFiber,
        '' + newChild,
        expirationTime,
      );
    }
    // 根据key是否相等复用或者创建新的Fiber
    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE: {
          if (newChild.key === key) {
            if (newChild.type === REACT_FRAGMENT_TYPE) {
              return updateFragment(
                returnFiber,
                oldFiber,
                newChild.props.children,
                expirationTime,
                key,
              );
            }
            return updateElement(
              returnFiber,
              oldFiber,
              newChild,
              expirationTime,
            );
          } else {
            return null;
          }
        }
        case REACT_PORTAL_TYPE: {
          if (newChild.key === key) {
            return updatePortal(
              returnFiber,
              oldFiber,
              newChild,
              expirationTime,
            );
          } else {
            return null;
          }
        }
      }

      if (isArray(newChild) || getIteratorFn(newChild)) {
        if (key !== null) {
          return null;
        }

        return updateFragment(
          returnFiber,
          oldFiber,
          newChild,
          expirationTime,
          null,
        );
      }

      throwOnInvalidObjectType(returnFiber, newChild);
    }
    return null;
  }

mapRemainingChildren

创建oldFiber的map的过程

typescript
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;
  }

deleteRemainingChildren

删除节点的方法

typescript
function deleteRemainingChildren(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
  ): null {
    // shouldTrackSideEffects 在第一次得时候是false,第一次mount的时候不需要管
    if (!shouldTrackSideEffects) {
      // Noop.
      return null;
    }
    // 逐个删除兄弟节点
    let childToDelete = currentFirstChild;
    while (childToDelete !== null) {
      deleteChild(returnFiber, childToDelete);
      childToDelete = childToDelete.sibling;
    }
    return null;
  }

deleteChild

typescript
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
    // shouldTrackSideEffects 在第一次得时候是false,第一次mount的时候不需要管
    if (!shouldTrackSideEffects) {
      // Noop.
      return;
    }
    // 将当前待删除的节点,放在父节点的effect连上
    const last = returnFiber.lastEffect;
    if (last !== null) {
      last.nextEffect = childToDelete;
      returnFiber.lastEffect = childToDelete;
    } else {
      returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
    }
    // 将待删除的节点的nextEffect置空,因为这个节点要被删除了,上面的副作用也没什么意义了
    childToDelete.nextEffect = null;
    // 打上待删除的标记 在后续commit阶段真正的删除
    childToDelete.effectTag = Deletion;
  }

备案号: 浙ICP备2023000081号