本文介绍my_react的最终版

在前作的基础上,新增了diff差异标记及commit阶段的全量更新。完整代码如下,不赘述了。


let nextUnitOfWork = null;
let wipRoot = null;
let currentRoot = null; // 新增:保存已提交的旧 Fiber 树(current 树)
let deletions = [];     // 新增:保存需要删除的 Fiber 节点

// 1. 标记类型常量(精简版)
const Placement = 'PLACEMENT'; // 新增节点
const Update = 'UPDATE';       // 属性更新
const Deletion = 'DELETION';   // 删除节点

// 创建文本节点 Fiber
const createTextNode = (child) => ({
  type: 'text',
  props: { nodeValue: child, children: [] }
});

// 创建元素 Fiber(虚拟DOM)
const myCreateElement = (type, props, ...children) => ({
  type,
  props: { ...props, children: children.map(child => typeof child === 'object' ? child : createTextNode(child)) }
});

// 创建真实 DOM 节点
const createDom = (fiber) => 
  fiber.type === 'text' 
    ? document.createTextNode(fiber.props.nodeValue) 
    : document.createElement(fiber.type);

// 对比新旧 Fiber 节点的属性差异
const updateProps = (dom, oldProps, newProps) => {
  // 1. 删除旧属性(新props中没有的)
  Object.keys(oldProps).forEach(key => {
    if (key !== 'children' && !newProps[key]) delete dom[key];
  });
  // 2. 更新/新增属性(新props有变化的)
  Object.keys(newProps).forEach(key => {
    if (key !== 'children' && oldProps[key] !== newProps[key]) {
      dom[key] = newProps[key];
    }
  });
};

// Diff 核心 - 复用/创建/删除 Fiber 节点
const reconcileChildren = (wipFiber, elements) => {
  let oldFiber = wipFiber.alternate?.child; // 旧 Fiber 子节点
  let prevSibling = null;

  elements?.forEach((element, index) => {
    const sameType = oldFiber && element && element.type === oldFiber.type;
    let newFiber = null;

    // 1. 类型相同 - 复用旧 Fiber,标记更新
    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber, // 关联旧 Fiber
        effectTag: Update
      };
    }
    // 2. 类型不同/无旧 Fiber - 新建 Fiber,标记新增
    if (!sameType && element) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: Placement
      };
    }
    // 3. 类型不同/无新元素 - 标记旧 Fiber 删除
    if (!sameType && oldFiber) {
      oldFiber.effectTag = Deletion;
      deletions.push(oldFiber);
    }

    if (oldFiber) oldFiber = oldFiber.sibling; // 移动到下一个旧 Fiber 兄弟节点

    // 建立 Fiber 树关联
    if (index === 0) wipFiber.child = newFiber;
    else if (element) prevSibling.sibling = newFiber;
    
    if (newFiber) prevSibling = newFiber;
  });

  // 剩余的旧 Fiber 全部标记删除
  while (oldFiber) {
    oldFiber.effectTag = Deletion;
    deletions.push(oldFiber);
    oldFiber = oldFiber.sibling;
  }
};

// 调和阶段:构建 Fiber 树 + Diff + 标记差异
const performUnitOfWork = (fiber) => {
  // 1. 创建 DOM(仅创建,不挂载)
  if (!fiber.dom && fiber.type) fiber.dom = createDom(fiber);

  // 2. Diff 对比子节点,标记差异
  reconcileChildren(fiber, fiber.props.children);

  // 3. 深度优先遍历:先处理子节点,再处理兄弟节点
  if (fiber.child) return fiber.child;
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) return nextFiber.sibling;
    nextFiber = nextFiber.parent;
  }
};

// 按标记执行 DOM 操作(提交阶段核心)
const commitWork = (fiber) => {
  if (!fiber) return;

  const domParent = fiber.parent.dom;
  // 1. 新增节点
  if (fiber.effectTag === Placement && fiber.dom) {
    domParent.appendChild(fiber.dom);
  }
  // 2. 更新节点属性
  else if (fiber.effectTag === Update && fiber.dom) {
    updateProps(fiber.dom, fiber.alternate.props, fiber.props);
  }
  // 3. 删除节点(单独收集,最后处理)

  // 递归处理子/兄弟节点
  commitWork(fiber.child);
  commitWork(fiber.sibling);
};

// 处理删除节点
const commitDeletions = (fiber, domParent) => {
  if (fiber.dom) domParent.removeChild(fiber.dom);
  else { // 非叶子节点,递归删除子节点
    commitDeletions(fiber.child, domParent);
  }
  if (fiber.sibling) commitDeletions(fiber.sibling, domParent);
};

// 提交阶段:按标记批量更新 DOM
const commitRoot = () => {
  // 1. 处理删除
  deletions.forEach(fiber => commitDeletions(fiber, fiber.parent.dom));
  // 2. 处理新增/更新
  commitWork(wipRoot.child);
  // 3. 保存当前 Fiber 树为旧树(供下次更新 Diff 使用)
  currentRoot = wipRoot;
  wipRoot = null;
  deletions = []; // 清空删除队列
};

// 触发渲染(初始化/更新都调用此方法)
const myRender = (element, container) => {
  wipRoot = {
    dom: container,
    props: { children: [element] },
    alternate: currentRoot // 关联旧 Fiber 树
  };
  nextUnitOfWork = wipRoot;
};

// 工作循环:可中断的调和阶段
const workLoop = (deadline) => {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1; // 剩余时间<1ms则让出主线程
  }

  // 调和完成,执行提交
  if (!nextUnitOfWork && wipRoot) commitRoot();
  requestIdleCallback(workLoop);
};

requestIdleCallback(workLoop);