mini-react 实现function 组件

代码结构如下:

  1. React.js 作用
  • render() : 渲染入口,初始化根 Fiber 并启动调度
  • createElement() : 将 JSX 转换为虚拟 DOM 对象
  • workLoop() : 调度器,利用 requestIdleCallback 进行时间切片
  • performUnitOfWork() : 处理单个 Fiber 工作单元
  • commitRoot() : 提交阶段入口
diff 复制代码
作用:实现一个非常精简的 Fiber 渲染器的核心逻辑(非完整实现,偏教学/探索用途)。
目标与分层:
- render(): 接收虚拟 DOM(由 JSX 或 createElement 创建),将整体渲染任务拆分为 fiber 工作单元并启动时间切片调度。
- performUnitOfWork(): 在 render 阶段构建/链接 fiber 节点并创建真实 DOM(但不立即挂载到页面上,挂载在 commit 阶段统一处理以优化性能)。
- commitRoot()/commitWork(): 将计算完成的 DOM 树统一提交到真实 DOM(避免 render 阶段频繁 DOM 操作)。
重要约定(fiber 结构):
- fiber.type: 节点类型(字符串标签或 'TEXT_ELEMENT')。
- fiber.props: 属性对象(包含 children 数组)。
- fiber.dom: 对应的真实 DOM 节点(在 render 阶段 createDom 后创建)。
- fiber.parent: 父 fiber 引用(用于 commit 时挂载)。
- fiber.child: 第一个子 fiber 链接(深度优先处理顺序)。
- fiber.sibling: 兄弟 fiber 链接(以支持广度上的顺序处理)。
设计说明(简要):
- 为支持页面响应性,渲染任务被拆分为多个小的工作单元(fiber),并通过 requestIdleCallback 的空闲时间片执行。
- render 阶段只负责构建 DOM 节点并建立 fiber 链表(depth-first 构建),真正的 DOM 插入在 commit 阶段一次性完成。
- 目前未

JavaScript

  1. ReactDom.js 作用:创建根节点
  • createRoot() : 创建根节点,提供渲染接口
  1. fiberUtils.js 作用
  • commitWork() : 递归挂载 Fiber 树到真实 DOM
sql 复制代码
功能:实现 Fiber 提交阶段的操作(commit),把 render 阶段构建的 fiber 树转为真实 DOM 结构。
说明:commit 阶段的核心是把已存在且正确设置 props/dom 的 fiber 节点插入文档中。该模块将提交逻辑单独抽离,
以便让渲染逻辑和提交逻辑职责分离,且更容易进行单元测试。

JavaScript

  1. domUtils.js
  • createDom() : 创建真实 DOM 节点
  • updateProperties() : 更新 DOM 属性
  • appendDomToRootContainer() : DOM 挂载逻辑
diff 复制代码
功能概览:
- 本文件包含与真实 DOM 操作相关的纯工具函数,负责在 render/commit 阶段处理 DOM 的创建、属性更新与父容器挂载逻辑。
- 把这些与副作用相关的细节抽象到独立模块,能使主渲染逻辑更简洁且更容易测试。
约束与说明:
- 这些函数会直接操作浏览器 DOM,因此它们的副作用应只在 commit 阶段执行。
- updateProperties 非常基础:仅直接把 props 的字段(非 children)赋值到元素上,未做差分、事件绑定处理或样式合并(这些为后续功能)。

JavaScript

  1. childrenUtils.js
  • normalizeChildren() : 规范化 children 数组
  • 处理各种输入类型(undefined、单值、数组)
  • 扁平化嵌套数组
  • 将文本节点转换为 TEXT_ELEMENT 结构
typescript 复制代码
功能:规范化 children 列表,返回一个扁平化且统一结构的 children 数组(便于后续构建 fiber)
这个工具做了下列工作:
1) 接受各种可能的 children 输入形态:
   - undefined / null
   - 单个节点对象(VNode)
   - 单个原始值(string / number)
   - 数组(可能嵌套)
2) 将 children 规范成一个扁平数组,移除 null/undefined
3) 将原始文本(string / number)转换为一个统一的 TEXT_ELEMENT 虚拟节点,
   以便后续的 createDom / props 处理一致
返回值:一个标准化的 children 数组(所有项都为 VNode 样式对象,或整体为空数组)
设计注意点:
- 此处没有深度拷贝 children 对象,因此传入的对象引用会保留。
- 对于非常深/大的 children 数组,flat(Infinity) 可能引发性能问题,生产代码中应使用更高效的迭代器或限制层级。

JavaScript

整体渲染流程

performUnitOfWork 详细流程

commitWork 提交流程

children规范化流程

详细执行流程

阶段一:初始化阶段

  1. 应用启动
  • main.jsx 调用 ReactDom.createRoot(document.getElementById('root'))
  • 创建根容器,返回包含 render 方法的对象
  1. 开始渲染
  • 调用 root.render(App)
  • 触发 React.render(node, container)
  1. 设置根 Fiber
  • 创建根 Fiber 对象,设置 dom 为容器节点
  • props.children 包含要渲染的 App 组件
  • 设置 nextWorkOfUnit 为根 Fiber
  • 保存 root 引用用于后续提交

阶段二:调度器启动

  1. 工作循环初始化
  • window.requestIdleCallback(workLoop) 启动调度
  • workLoop 检查浏览器空闲时间
  1. 时间切片处理
  • 在 deadline.timeRemaining() > 1ms 时持续处理
  • 每次循环调用 performUnitOfWork(nextWorkOfUnit)
  • 处理完成后更新 nextWorkOfUnit 为返回的下一个 Fiber

阶段三:Render 阶段(Fiber 树构建)

3.1 处理单个 Fiber 单元

  1. Fiber 类型判断
  • 检查是否为函数组件(typeof fiber.type === 'function')
  • 函数组件:执行函数获取返回的 VNode,更新 fiber.props.children
  • 普通组件:创建 DOM 节点并设置属性
  1. DOM 节点创建
  • 调用 createDom(fiber) 创建对应节点
  • TEXT_ELEMENT 创建 TextNode,其他创建 Element
  • 调用 updateProperties 设置 DOM 属性
  1. 子节点规范化
  • 调用 normalizeChildren(fiber.props.children)
  • 处理各种 children 输入情况
  • 扁平化数组,转换文本节点为 TEXT_ELEMENT
  • 过滤 null/undefined 节点
  1. 构建子 Fiber 链表
  • 遍历规范化后的 children 数组
  • 为每个子节点创建对应的 Fiber
  • 建立 parent-child-sibling 链接关系
  • 第一个子节点:fiber.child = newFiber
  • 后续子节点:prevSibling.sibling = newFiber

3.2 深度优先遍历

  1. 返回下一个工作单元
  • 优先返回子节点:if (fiber.child) return fiber.child
  • 其次返回兄弟节点:if (fiber.sibling) return fiber.sibling
  • 向上回溯查找父级的兄弟节点
  • 遍历完成返回 null

阶段四:Commit 阶段(DOM 挂载)

4.1 提交准备

  • 当 nextWorkOfUnit 为 null 且 root 存在时
  • 调用 commitRoot() 进入提交阶段

4.2 递归挂载 DOM

  1. commitWork 执行
  • 检查当前 Fiber 是否有 DOM 节点
  • 查找最近的有 DOM 的父节点(跳过函数组件)
  • 执行 parentFiber.dom.appendChild(fiber.dom)
  1. 深度优先挂载
  • 先递归挂载子节点:commitWork(fiber.child)
  • 再递归挂载兄弟节点:commitWork(fiber.sibling)

4.3 完成渲染

  • 所有 DOM 节点挂载完成
  • 清空 root 引用
  • 渲染流程结束

四、关键数据结构

Fiber 节点结构:

javascript

yaml 复制代码
{
  type: 'div' | 'TEXT_ELEMENT' | Function,
  props: { children: [], ...attributes },
  dom: HTMLElement | Text,
  parent: Fiber,
  child: Fiber,
  sibling: Fiber
}

Plain Text

虚拟 DOM 结构:

javascript

css 复制代码
// 元素节点
{ type: 'div', props: { id: 'app', children: [...] } }


// 文本节点  
{ type: 'TEXT_ELEMENT', props: { nodeValue: 'text', children: [] } }

Plain Text

五、总结

1. 时间切片调度

  • 使用 requestIdleCallback 避免阻塞主线程
  • 将渲染任务拆分为多个工作单元
  • 在浏览器空闲时段执行

2. 阶段分离设计

  • Render 阶段:纯计算,构建 Fiber 树,不操作 DOM
  • Commit 阶段:批量 DOM 操作,避免频繁重排重绘

3. 链表数据结构

  • 使用 child、sibling 指针构建树形结构
  • 支持高效的深度优先遍历
  • 便于暂停和恢复渲染任务

4. 统一节点处理

  • 所有节点(包括文本)都转换为统一结构
  • 简化后续处理逻辑
  • 支持函数组件和普通组件

5. 模块化设计

  • 每个模块职责单一明确
  • 工具函数独立,便于测试和维护
  • 清晰的依赖关系和数据流向

六、扩展性考虑

当前架构为后续功能扩展提供了良好基础,后续需要做这些:

  • Diff 算法:可在 performUnitOfWork 中实现节点比较
  • 事件系统:在 updateProperties 中添加事件处理
  • Hooks 支持:在函数组件处理时维护状态
  • 错误边界:添加异常捕获和处理机制React Fiber 渲染器核心逻辑详解
相关推荐
AlienZHOU5 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc8 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师10 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen10 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒11 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow11 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81811 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007811 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明11 小时前
第 6 章 组件进阶
前端·vue.js
江华森11 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端