Skip to content

04.多节点 domdiff

js
/**
 * 设置子fiber节点的位置,并在必要的时候添加Placement标志
 * @param {Object} newFiber 新的子fiber节点
 * @param {number} lastPlacedIndex 最后一个被放置的节点的index
 * @param {number} newIdx 新节点的index
 * @return {number} 返回最后一个被放置的节点的index
 */
function placeChild(newFiber, lastPlacedIndex, newIdx) {
  newFiber.index = newIdx;
  if (!shouldTrackSideEffects) {
    return lastPlacedIndex;
  }
  const current = newFiber.alternate;
  if (current !== null) {
    const oldIndex = current.index;
    if (oldIndex < lastPlacedIndex) {
      newFiber.flags |= Placement;
      return lastPlacedIndex;
    } else {
      return oldIndex;
    }
  } else {
    newFiber.flags |= Placement;
    return lastPlacedIndex;
  }
}

/**
 * 更新元素节点
 * @param {Object} returnFiber 父级fiber节点
 * @param {Object} current 当前fiber节点
 * @param {Object} element 新的React元素
 * @return {Object} 返回一个更新后的fiber节点
 */
function updateElement(returnFiber, current, element) {
  const elementType = element.type;
  if (current !== null) {
    if (current.type === elementType) {
      const existing = useFiber(current, element.props);
      existing.return = returnFiber;
      return existing;
    }
  }
  const created = createFiberFromElement(element);
  created.return = returnFiber;
  return created;
}

/**
 * 更新一个slot,如果新的子节点和旧的fiber节点匹配,则返回更新后的fiber节点,否则返回null
 * @param {Object} returnFiber 父级fiber节点
 * @param {Object|null} oldFiber 旧的fiber节点
 * @param {any} newChild 新的子节点
 * @return {Object|null} 返回一个更新后的fiber节点,如果新的子节点和旧的fiber节点不匹配,则返回null
 */
function updateSlot(returnFiber, oldFiber, newChild) {
  const key = oldFiber !== null ? oldFiber.key : null;
  if (newChild !== null && typeof newChild === "object") {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE: {
        if (newChild.key === key) {
          return updateElement(returnFiber, oldFiber, newChild);
        }
      }
      default:
        return null;
    }
  }
  return null;
}

/**
 * 将剩余的子节点映射到一个Map对象中
 * @param {Object|null} returnFiber 父级fiber节点
 * @param {Object|null} existingChildren 已经存在的子节点
 * @return {Map|null} 返回一个Map对象,键是子节点的key,值是子节点的fiber节点
 */
function mapRemainingChildren(returnFiber, currentFirstChild) {
  const existingChildren = 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;
}
/**
 * 如果当前节点是文本节点则复用,否则创建新的文本节点。
 * @param {Fiber} returnFiber - 父级Fiber节点
 * @param {Fiber} current - 当前处理的Fiber节点
 * @param {string} textContent - 文本内容
 * @returns {Fiber} 新的或者复用的文本节点
 */
function updateTextNode(returnFiber, current, textContent) {
  if (current === null || current.tag !== HostText) {
    const created = createFiberFromText(textContent);
    created.return = returnFiber;
    return created;
  } else {
    const existing = useFiber(current, textContent);
    existing.return = returnFiber;
    return existing;
  }
}
/**
 * 从现有的子节点映射中更新Fiber节点
 * @param {Map} existingChildren - 现有的子节点映射
 * @param {Fiber} returnFiber - 父级Fiber节点
 * @param {number} newIdx - 新节点的索引
 * @param {any} newChild - 新的子节点
 * @returns {Fiber} 更新后的Fiber节点
 */
function updateFromMap(existingChildren, returnFiber, newIdx, newChild) {
  if (
    (typeof newChild === "string" && newChild !== "") ||
    typeof newChild === "number"
  ) {
    const matchedFiber = existingChildren.get(newIdx) || null;
    return updateTextNode(returnFiber, matchedFiber, "" + newChild);
  }
  if (typeof newChild === "object" && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE: {
        const matchedFiber =
          existingChildren.get(newChild.key === null ? newIdx : newChild.key) ||
          null;
        return updateElement(returnFiber, matchedFiber, newChild);
      }
    }
  }
}
/**
 * 将新的子节点数组与旧的子Fiber进行比较,并返回新的子Fiber
 *
 * @param {Fiber} returnFiber - 新的父Fiber
 * @param {Fiber} currentFirstChild - 老fiber第一个子fiber
 * @param {Array} newChildren - 新的子节点数组
 * @return {Fiber} resultingFirstChild - 返回的新的子Fiber
 */
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
  let resultingFirstChild = null;
  let previousNewFiber = null;
  let newIdx = 0;
  let oldFiber = currentFirstChild;
  let nextOldFiber = null;
  let lastPlacedIndex = 0;

  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    nextOldFiber = oldFiber.sibling;
    const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
    if (newFiber === null) {
      break;
    }
    if (shouldTrackSideEffects) {
      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) {
    deleteRemainingChildren(returnFiber, oldFiber);
    return resultingFirstChild;
  }

  if (oldFiber === null) {
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = createChild(returnFiber, newChildren[newIdx]);
      if (newFiber === null) continue;
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
  }

  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

  for (; newIdx < newChildren.length; newIdx++) {
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx]
    );
    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) {
    existingChildren.forEach((child) => deleteChild(returnFiber, child));
  }
  return resultingFirstChild;
}

基于 VitePress 构建