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 渲染器核心逻辑详解
相关推荐
用户89225411829011 小时前
游戏框架文档
前端
Yanni4Night1 小时前
JS 引擎赛道中的 Rust 角色
前端·javascript
欧阳的棉花糖1 小时前
纯Monorepo vs 混合式Monorepo
前端·架构
北辰alk1 小时前
Vue3 异步组件深度解析:提升大型应用性能与用户体验的完整指南
前端·vue.js
明远湖之鱼2 小时前
浅入理解流式SSR的性能收益与工作原理
前端·ios
IT_陈寒2 小时前
Python性能提升50%:这5个隐藏技巧让你的代码快如闪电⚡
前端·人工智能·后端
懒人村杂货铺3 小时前
微前端QianKun的使用以及坑点问题
前端
qq_366577513 小时前
Vue3创建项目,只能localhost访问问题处理
前端·javascript·vue.js
一个处女座的程序猿O(∩_∩)O3 小时前
React Router 路由模式详解:HashRouter vs BrowserRouter
前端·react.js·前端框架