从零实现React核心架构:虚拟DOM、Fiber与协调算法

各位前端大佬们,今天将分享一个简化版React的核心实现,包含虚拟DOM、Fiber架构、协调算法等关键模块。通过200行代码,我们将理解React如何实现高效渲染。当然也是在网上找视频跟着学习的,自己跟着手敲了一遍。


核心架构流程

rust 复制代码
jsx -> babel/swc转换 -> createElement生成虚拟DOM -> Fiber架构处理 -> Diff算法 -> DOM提交

核心模块解析

1. 虚拟DOM构建

javascript 复制代码
const React = {
  createElement(type, props, ...children) {
    // 将子节点包装为TEXT_ELEMENT类型
    return { type, props: { ...props, children } }
  },
  createTextElement(text) {
    return { type: "TEXT_ELEMENT", props: { nodeValue: text } }
  }
}
  • 作用:将JSX转换为虚拟DOM对象
  • 特点
    • 递归处理子节点
    • 文本节点特殊标记为TEXT_ELEMENT

2. Fiber架构核心

关键变量:

javascript 复制代码
let nextUnitOfWork = null  // 当前处理的Fiber节点
let wipRoot = null         // 当前Fiber树根节点
let currentRoot = null     // 前次渲染的Fiber树
let deletions = []         // 待删除节点集合

工作循环:

javascript 复制代码
function workLoop(deadline) {
  while (nextUnitOfWork && 剩余时间>1ms) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
  if (!nextUnitOfWork && wipRoot) commitRoot()
  requestIdleCallback(workLoop)
}
  • 时间切片 :通过requestIdleCallback在浏览器空闲时段执行任务
  • 可中断渲染:每次处理单个Fiber节点后检查剩余时间

performUnitOfWork与reconcileChildren协作流程解析


Fiber节点处理流水线

1. performUnitOfWork核心流程

javascript 复制代码
function performUnitOfWork(fiber) {
  // 步骤1:创建当前节点的DOM(若不存在)
  if (!fiber.dom) 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;          // 最后回溯父节点
  }
}
执行逻辑示意图:
php 复制代码
父Fiber
    ↓ (child)
子Fiber → (sibling) → 兄弟Fiber
    ↓ (child)
孙Fiber

2. reconcileChildren工作原理

javascript 复制代码
function reconcileChildren(fiber, elements) {
  let index = 0;
  let oldFiber = fiber.alternate?.child; // 获取旧Fiber子节点
  let prevSibling = null;

  while (index < elements.length || oldFiber) {
    const element = elements[index];
    // 新旧节点对比决策树
    const sameType = oldFiber && element?.type === oldFiber.type;

    // 生成新Fiber的三种情况
    if (sameType) { /* 更新逻辑 */ }
    if (!sameType && element) { /* 新建逻辑 */ }
    if (oldFiber && !sameType) { /* 删除逻辑 */ }

    // 构建链表结构
    if (index === 0) fiber.child = newFiber;
    else prevSibling.sibling = newFiber;
    
    prevSibling = newFiber;
    index++;
  }
}
协作流程图解:
bash 复制代码
───────────────────────────────────────
 performUnitOfWork
      │
      ├─ 1. 创建DOM(如需)
      │
      └─ 2. 调用reconcileChildren
              │
              ├─ 对比新旧子节点
              ├─ 生成带effectTag的Fiber
              └─ 构建child/sibling链表
───────────────────────────────────────

关键协作机制详解

1. 深度优先遍历

  • 子节点优先:总是先处理当前Fiber的子节点
  • 链表回溯:当子节点处理完成后,通过sibling指针横向移动
  • 完成条件:当没有子节点和兄弟节点时,结束当前分支

2. 增量构建策略

javascript 复制代码
// 示例:处理<div>包含两个<span>的情况
父Fiber (div)
   ↓ child
子Fiber1 (span1)
   ↓ sibling
子Fiber2 (span2)
  • reconcileChildren为每个子元素创建Fiber节点
  • 通过childsibling指针形成链表结构
  • performUnitOfWork按深度优先顺序遍历链表

3. 操作类型标记

在协调过程中会为每个Fiber节点打标:

  • PLACEMENT:需要插入DOM的新节点
  • UPDATE:需要属性更新的已有节点
  • DELETION:需要移除的旧节点

完整协作示例

假设有如下更新操作:

javascript 复制代码
// 初始渲染
<div>
  <span>Hello</span>
</div>

// 更新后
<div>
  <p>World</p>
</div>

执行步骤:

  1. performUnitOfWork处理div Fiber
  2. reconcileChildren对比新旧span和p
    • 标记span为DELETION
    • 创建p的PLACEMENT Fiber
  3. 提交阶段先删除span节点
  4. 插入新的p节点

整个过程通过requestIdleCallback分片执行,实现无阻塞渲染。

ini 复制代码
### 3. 协调算法(Diff)
```javascript
function reconcileChildren(fiber, elements) {
  let oldFiber = fiber.alternate?.child
  while (元素未遍历完 || 旧Fiber存在) {
    const sameType = oldFiber与当前元素类型相同
    
    if (sameType) {
      // 更新节点
      newFiber = { ...复用旧DOM, effectType: "UPDATE" }
    } else if (新元素存在) {
      // 新建节点
      newFiber = createFiber(element)
    } else if (旧Fiber存在) {
      // 删除节点
      oldFiber.effectType = "DELETION"
      deletions.push(oldFiber)
    }
    
    // 构建链表结构
    if (是第一个子节点) fiber.child = newFiber
    else preSibling.sibling = newFiber
  }
}
  • 三种操作类型
    1. UPDATE:类型相同,更新属性
    2. PLACEMENT:新增节点
    3. DELETION:移除旧节点

4. DOM提交阶段

javascript 复制代码
function commitRoot() {
  deletions.forEach(commitWork)  // 先处理删除
  commitWork(wipRoot.child)      // 递归提交变更
}

function commitWork(fiber) {
  if (fiber.effectType === "PLACEMENT") {
    parent.dom.appendChild(fiber.dom)
  } else if (fiber.effectType === "UPDATE") {
    updateDom(fiber.dom, oldProps, newProps)
  } else if (fiber.effectType === "DELETION") {
    parent.dom.removeChild(fiber.dom)
  }
  // 深度优先遍历
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}
  • 批量处理:先处理所有删除操作,再递归提交其他变更
  • DOM操作:最终将Fiber树映射到真实DOM

完整代码实现

javascript 复制代码
// React整体架构
// jsx => babel|swc => React.createElement => ReactDom.render => FIber => diff算法 => commit阶段

const React = {
  createElement(type, props, ...children) {
    return {
      type,
      props: {
        ...props,
        children: children.map((child) => {
          if (typeof child === "object") {
            return child;
          }
          return React.createTextElement(child);
        }),
      },
    };
  },

  createTextElement(text) {
    return {
      type: "TEXT_ELEMENT",
      props: {
        nodeValue: text,
        children: [],
      },
    };
  },
};

// 实现虚拟dom转fiber结构和时间切片
let nextUnitOfWork = null;
let wipRoot = null;
let currentRoot = null;
let deletions = null;

function render(element, container) {
  wipRoot = {
    dom: container,
    props: { children: [element] },
    alternate: currentRoot,
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

function createDom(fiber) {
  const dom = fiber.type === "TEXT_ELEMENT" 
    ? document.createTextNode("")
    : document.createElement(fiber.type);
  updateDom(dom, {}, fiber.props);
  return dom;
}

function updateDom(dom, preProps, nextProps) {
  Object.keys(preProps).forEach(name => {
    if (name !== "children") dom[name] = "";
  });
  Object.keys(nextProps).forEach(name => {
    if (name !== "children") dom[name] = nextProps[name];
  });
}

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }
  if (!nextUnitOfWork && wipRoot) commitRoot();
  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

function performUnitOfWork(fiber) {
  if (!fiber.dom) fiber.dom = createDom(fiber);
  reconcileChildren(fiber, fiber.props.children);
  
  if (fiber.child) return fiber.child;
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) return nextFiber.sibling;
    nextFiber = nextFiber.parent;
  }
}

function reconcileChildren(fiber, elements) {
  let index = 0;
  let oldFiber = fiber.alternate?.child;
  while (index < elements.length || oldFiber) {
    const element = elements[index];
    let newFiber = null;
    const sameType = oldFiber && element?.type === oldFiber.type;

    if (sameType) {
      newFiber = { ...oldFiber, props: element.props, effectType: "UPDATE" };
    }
    if (!sameType && element) {
      newFiber = { type: element.type, props: element.props, effectType: "PLACEMENT" };
    }
    if (oldFiber && !sameType) {
      oldFiber.effectType = "DELETION";
      deletions.push(oldFiber);
    }

    // 构建链表结构...
    index++;
  }
}

function commitRoot() {
  deletions.forEach(commitWork);
  commitWork(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) return;
  const domParent = fiber.parent.dom;
  if (fiber.effectType === "PLACEMENT" && fiber.dom) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectType === "UPDATE") {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectType === "DELETION") {
    domParent.removeChild(fiber.dom);
  }
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

// 测试用例
render(
  React.createElement("div", { id: "box" }, 
    React.createElement("span", null, "hello")
  ),
  document.getElementById("root")
);

总结

本实现核心展示了:

  1. 虚拟DOM构建:通过createElement创建轻量级JS对象
  2. Fiber架构:将渲染任务拆分为可中断的工作单元
  3. 协调算法:通过类型比较实现高效DOM更新
  4. 提交阶段:批量处理DOM操作保证性能

完整呈现了React核心渲染机制。建议各位大佬可以自己实现一次,以便于有时间可以拿出来回顾回顾,达到充分理解的程度。

相关推荐
No Silver Bullet2 小时前
React Native进阶(六十一): WebView 替代方案 react-native-webview 应用详解
javascript·react native·react.js
liruiqiang052 小时前
卷积神经网络 - 梯度和反向传播算法
人工智能·深度学习·神经网络·算法·机器学习·cnn
人类群星闪耀时3 小时前
大数据中的数据预处理:脏数据不清,算法徒劳!
大数据·算法
present--014 小时前
【leetcode题解】贪心算法
算法·贪心算法
ohnoooo94 小时前
代码随想录算法训练营第38天 | 322. 零钱兑换 279.完全平方数 139.单词拆分 背包问题总结
算法
小楠小楠小楠5 小时前
Leetcode-动态规划
算法·leetcode·动态规划
陈大鱼头5 小时前
都 2025 年,Next.js 竟然还能被爆出这种低级 Bug? 😅
前端·react.js·next.js
Aurora_wmroy5 小时前
算法竞赛备赛——【数据结构】栈&单调栈
数据结构·c++·算法·蓝桥杯
CodeJourney.5 小时前
文献检索与下指南
数据库·人工智能·python·算法
三金同学6 小时前
基于babel做一个简单的react网页编辑器
react.js