不只是mini-react第二节:实现最简fiber
省流|总结
首先,我们编写JSX文件,并通过Babel等转换工具将其转化为createElement()函数的调用,最终生成虚拟 DOM(Vdom)格式。举个例子:
// 原始 JSX
const App = <div>hi-mini-react</div>;// Babel 编译后
const App = React.createElement("div",null,"hi-mini-react"
);// createElement 返回的虚拟 DOM 结构
const App = {type: "div",props: {children: ["hi-mini-react"]}
};
接下来,将转换后的虚拟 DOM 格式传入render函数,启动fiber流程,调用代码如下:
ReactDOM.createRoot(document.querySelector("#root")).render(App);
这段代码的作用是将整个App组件渲染到root容器中。
然后,fiber流程启动。render函数接收传入的虚拟 DOM,并构建root fiber节点,即fiber树的根节点,也是第一个工作单元nextWorkOfUnit。此时,fiber树中仅包含根节点的真实 DOM 引用(容器),其他节点尚未创建。root fiber节点具有如下特点:
- 它的
dom属性指向容器节点 - 它的
props.children包含整个应用的虚拟 DOM 结构 - 它是整个
fiber树的起点
nextWorkOfUnit = {dom: container, // 真实 DOM 元素props: {children: [el], // el 是传入的 App 虚拟 DOM},
};
接下来,启用workLoop任务调度系统。该系统通过requestIdleCallback在浏览器空闲时执行任务。当检测到剩余时间不足(小于 1ms)时,系统会主动让出主线程,从而实现任务的分片处理。
每次workLoop循环都会调用performWorkOfUnit,确保每次只处理一个fiber节点,实现任务的分片执行,这些任务可以随时中断与恢复。如果当前fiber节点的真实 DOM 引用不存在,performWorkOfUnit会在此fiber节点上添加dom引用,并将该fiber节点指向父级fiber节点,同时将其真实 DOM 引用传递给父级节点,形成如下结构:
//可视化展示
fiber节点 真实DOM节点
┌─────────┐ ┌─────────┐
│div1 fiber│ dom引用 │ <div> │
├─────────┤ ────────────► ├─────────┤
│parent │ │ │
│dom │ │ │
└─────────┘ └─────────┘▲ ▲│ ││ parent │ append(h1)│ │
┌─────────┐ ┌─────────┐
│h1 fiber │ dom引用 │ <h1> │
├─────────┤ ────────────► ├─────────┤
│parent │ │ │
│dom │ │ │
└─────────┘ └─────────┘//具体代码const dom = (fiber.dom = createDom(fiber.type));fiber.parent.dom.append(dom);
接着,performWorkOfUnit会调用两个函数:
updateProps:读取fiber节点中的虚拟 DOM 属性,并将其应用到对应的真实 DOM 上。initChildren:将当前fiber节点传入,通过深度优先遍历的方式,将当前树形结构的fiber节点转化为链表结构。这样,大的fiber节点被拆分成一个个小的fiber节点,最终形成自上而下的树形结构。这种方式将整个渲染过程分割成一个个小任务,每个任务处理一个节点,从而实现任务的分片,避免了长任务阻塞主线程。
最后,遍历由initChildren构建的链表结构。需要注意的是,这里并不是先遍历再执行,而是边遍历边执行。
最简任务调度器workLoop(时间分片)
- 首次调用
requestIdleCallback(workLoop),浏览器会在空闲时执行workLoop函数。 workLoop函数会执行任务,并在每次执行任务时检查剩余空闲时间。- 如果空闲时间不足 1 毫秒(
timeRemaining() < 1),shouldYield会被设置为true,从而暂停当前任务。 - 然后,
requestIdleCallback(workLoop)会被调用来请求下一次空闲时间,从而在下次空闲时继续执行未完成的任务。 - 每次任务执行时,
taskId都会递增,用于标记任务的顺序。
什么是下次空闲时?这里的下次空闲时是指当前可能有高优先级任务需要处理,那么浏览器会暂停当前相对低优先级的任务而去处理这个高优先级任务,等这个高优先级任务处理完成后再回过头来执行低优先级任务,这就是时间分片。
let taskId = 1;
function workLoop(deadline) {taskId++;let shouldYield = false;while (!shouldYield) {// run taskconsole.log(`taskId:${taskId} run task`);// domshouldYield = deadline.timeRemaining() < 1;}requestIdleCallback(workLoop);// 再次调用
}requestIdleCallback(workLoop);//首次调用
实现最简fiber
执行顺序:
jsx→babel等工具编译→createElement()→render()→workLoop()→performWorkOfUnit()→initChildren()→树形结构转链表结构
1.通过createElement创建虚拟DOM树
createElement函数负责创建一个虚拟 DOM 对象。它的参数包括:type(元素类型,如div、span等),props(元素属性),children(元素的子节点)。在 React 中,children的处理方式非常重要,因为它决定了如何渲染子元素。createElement会递归处理children,如果children是文本节点,则直接将其转换为文本节点。如果是其他 React 元素或组件,它会再次调用createElement来生成对应的虚拟 DOM。
// 1. 首先通过createElement创建React元素
function createElement(type, props, ...children) {return {type,props: {...props,children: children.map((child) => {// 如果子元素是字符串,则创建文本节点return typeof child === "string" ? createTextNode(child) : child;}),},};
}
2.调用render函数,设置工作单元(fiber节点)nextWorkOfUnit
由前一节博客可以知道:
el为编写的jsx文件
container为传入的真实dom节点(在这里是root节点)
render函数是 React 渲染流程的起点。它会将传入的虚拟 DOM(el)渲染到指定的容器(container)中。在这个过程中,React 会为每个虚拟 DOM 元素创建一个 fiber 对象,并构建一个 fiber 树。每个 fiber 节点包含对应的 DOM 元素、props、children等信息。这样 React 就能根据这个 fiber 树来管理和更新实际的 DOM。
// 2. 调用render函数开始渲染
function render(el, container) {// 创建第一个工作单元(root fiber)nextWorkOfUnit = {dom: container,//传入的真实root节点//虚拟dom元素props: {children: [el],},};
}
3.工作循环workLoop启动
workLoop是 React 渲染任务的调度器。它会根据浏览器的空闲时间进行任务调度,并决定什么时候更新哪些 fiber 节点。当浏览器空闲时,workLoop会通过requestIdleCallback触发任务执行。如果当前任务无法在空闲时间内完成,workLoop会暂停任务,等待下一次空闲时间。通过这种异步调度方式,React 可以避免阻塞主线程,确保渲染操作的流畅性。
// 3. 程序启动时就开始监听空闲时间
let nextWorkOfUnit = null;
function workLoop(deadline) {// deadline对象包含:// timeRemaining(): 返回当前空闲期剩余的毫秒数// didTimeout: 表示任务是否超时let shouldYield = false;while (!shouldYield && nextWorkOfUnit) {// 1. 条件判断:// - 是否需要让出主线程(shouldYield)// - 是否还有工作要做(nextWorkOfUnit)// 2. 处理当前工作单元并重新分配工作单元nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);// 3. 检查是否需要让出主线程shouldYield = deadline.timeRemaining() < 1;}// 4. 无论是否完成所有工作,都继续监听下一个空闲时间requestIdleCallback(workLoop);
}
4.performWorkOfUnit重新分配工作单元
performWorkOfUnit是处理每个 fiber 节点的函数。在执行过程中,它会根据虚拟 DOM 的type和props更新实际 DOM。当 fiber 节点包含子节点时,performWorkOfUnit会递归处理子节点。每处理完一个节点,performWorkOfUnit会返回该节点的下一个待处理工作单元,从而继续构建 DOM 树。
function performWorkOfUnit(fiber) {// 4 如果没有DOM节点,创建DOMif (!fiber.dom) {const dom = (fiber.dom = createDom(fiber.type));fiber.parent.dom.append(dom);//5 属性协调updateProps(dom, fiber.props);}
//---------------------------------------只关注前面即可// 6 处理子节点,构建fiber树initChildren(fiber)// 7 返回下一个工作单元,按照以下优先级:// 先找子节点if (fiber.child) {return fiber.child;}// 没有子节点找兄弟节点if (fiber.sibling) {return fiber.sibling;}// 都没有就回到父节点的兄弟节点return fiber.parent?.sibling;
}
5.updateProps 属性协调
updateProps 函数的核心目的是将 React 元素的属性(props)设置到实际的 DOM 节点上,而这里传入的dom实际上就是fiber.dom
function updateProps(dom, props) {Object.keys(props).forEach((key) => {if (key !== "children") {dom[key] = props[key];}});
}
举个简单的例子:
// 假设我们写了这样一个React元素
<div className="box" id="main">Hello</div>// React会将其转换为这样的对象
{type: 'div',props: {className: 'box',id: 'main',children: ['Hello']}
}// 然后在performWorkOfUnit中:
// 1. 首先创建DOM节点
const dom = document.createElement('div') // <div></div>// 2. 调用updateProps设置属性
updateProps(dom, props)
// 结果:<div class="box" id="main"></div>
可视化展示:
React元素的属性 → 真实DOM节点的属性{ <divclassName: 'box' → class="box"id: 'main' → id="main"children: [...] >} </div>React属性世界 DOM属性世界┌──────────┐ ┌──────────┐│ props │ ═══> │ DOM │└──────────┘ └──────────┘│ │className: 'box' class="box"onClick: fn addEventListener
6.initChildren子节点处理
initChildren函数的任务是初始化虚拟 DOM 中的子节点。它会遍历子节点,并为每个子节点创建一个新的 fiber 节点。每个新创建的 fiber 节点将被添加到父节点的children数组中,从而形成树形结构。React 会继续递归处理每个子节点,直到所有子节点都被处理为止。
function initChildren(fiber) {const children = fiber.props.children;let prevChild = null;// 遍历所有子节点,建立fiber链接关系children.forEach((child, index) => {const newFiber = {type: child.type,props: child.props,child: null,//子级parent: fiber,//父级sibling: null,//兄弟级dom: null,};// 第一个子节点设为childif (index === 0) {fiber.child = newFiber;} else {// 其他子节点设为上一个节点的siblingprevChild.sibling = newFiber;}prevChild = newFiber;});
}
7.performWorkOfUnit将树形结构通过深度遍历转化为链表结构
在performWorkOfUnit函数中,React 通过深度优先遍历将树形结构转化为链表结构。这种链表结构可以帮助 React 更高效地遍历并更新每个节点。每个 fiber 节点不仅包含当前节点的信息,还持有对父节点、兄弟节点的引用,这使得 React 可以灵活地在渲染过程中调整工作单元。
function performWorkOfUnit(fiber) {// ... 前面的代码省略 ...// 这三行代码决定了下一个工作单元,实际上就是在构建链表if (fiber.child) {return fiber.child; // 1️⃣ 优先返回子节点}if (fiber.sibling) {return fiber.sibling; // 2️⃣ 没有子节点就返回兄弟节点}return fiber.parent?.sibling; // 3️⃣ 都没有就返回父节点的兄弟节点
}
源代码
// 创建文本节点
function createTextNode(text) {console.log("heiheihei!!!!!!!");return {type: "TEXT_ELEMENT",props: {nodeValue: text,children: [],},};
}// 创建React元素
// type: 元素类型
// props: 元素属性
// children: 子元素
function createElement(type, props, ...children) {return {type,props: {...props,children: children.map((child) => {return typeof child === "string" ? createTextNode(child) : child;}),},};
}// 渲染函数:将React元素渲染到容器中
function render(el, container) {nextWorkOfUnit = {dom: container,props: {children: [el],},};
}// 下一个工作单元
let nextWorkOfUnit = null;// 工作循环:利用浏览器空闲时间处理任务
function workLoop(deadline) {let shouldYield = false;while (!shouldYield && nextWorkOfUnit) {nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);// 当剩余时间小于1ms时,让出主线程shouldYield = deadline.timeRemaining() < 1;}requestIdleCallback(workLoop);
}// 根据类型创建DOM节点
function createDom(type) {return type === "TEXT_ELEMENT"? document.createTextNode(""): document.createElement(type);
}// 更新DOM节点的属性
function updateProps(dom, props) {Object.keys(props).forEach((key) => {if (key !== "children") {dom[key] = props[key];}});
}// 初始化fiber节点的子节点
// 构建fiber树的链表结构:child(第一个子节点)和sibling(兄弟节点)
function initChildren(fiber) {const children = fiber.props.children;let prevChild = null;children.forEach((child, index) => {const newFiber = {type: child.type,props: child.props,child: null,parent: fiber,sibling: null,dom: null,};if (index === 0) {fiber.child = newFiber;} else {prevChild.sibling = newFiber;}prevChild = newFiber;});
}// 处理工作单元
// 主要完成三件事:
// 1. 创建DOM节点
// 2. 处理props
// 3. 构建fiber树
// 4. 返回下一个工作单元
function performWorkOfUnit(fiber) {// 1. 创建DOM节点,保证一次只处理一个fiber节点(任务分片机制)if (!fiber.dom) {const dom = (fiber.dom = createDom(fiber.type));// 将DOM节点添加到父节点fiber.parent.dom.append(dom);// 2. 处理propsupdateProps(dom, fiber.props);}// 3. 构建fiber树initChildren(fiber)// 4. 返回下一个要执行的任务// 遍历顺序:先子节点,然后兄弟节点,最后回到父节点的兄弟节点if (fiber.child) {return fiber.child;}if (fiber.sibling) {return fiber.sibling;}return fiber.parent?.sibling;
}// 启动工作循环
requestIdleCallback(workLoop);// React对象
const React = {render,createElement,
};export default React;
相关文章:
不只是mini-react第二节:实现最简fiber
省流|总结 首先,我们编写JSX文件,并通过Babel等转换工具将其转化为createElement()函数的调用,最终生成虚拟 DOM(Vdom)格式。举个例子: // 原始 JSX const App <div>hi-mini-react</div>;//…...
C++实现设计模式---命令模式 (Command)
命令模式 (Command) 命令模式 是一种行为型设计模式,它将请求封装为一个对象,从而使得可以用不同的请求对客户端进行参数化、对请求排队或记录日志,以及支持可撤销的操作。 意图 将操作的调用者与接收者分离,通过将请求封装为独…...
设计模式的艺术-享元模式
结构性模式的名称、定义、学习难度和使用频率如下表所示: 1.如何理解享元模式 当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。 在享元模式中,存储这些共享实例对象的地方称为享元池&…...
Linux的权限和一些shell原理
目录 shell的原理 Linux权限 sudo命令提权 权限 文件的属性 ⽂件类型: 基本权限: chmod改权限 umask chown 该拥有者 chgrp 改所属组 最后: 目录权限 粘滞位 shell的原理 我们广义上的Linux系统 Linux内核Linux外壳 Linux严格…...
【Postgres_Python】使用python脚本批量创建和导入多个PG数据库
之前批量创建和导入数据库分为2个python脚本进行,现整合优化代码合并为一个python脚本,可同步实现数据库的创建和数据导入。之前的文章链接: 【Postgres_Python】使用python脚本批量创建PG数据库 【Postgres_Python】使用python脚本将多个.S…...
Ubuntu安装GitLab
在 Ubuntu 上安装 GitLab 的步骤如下。这里以 GitLab Community Edition(CE)为例: 前提条件 确保你的 Ubuntu 系统是 20.04 或更高版本。确保你的系统满足 GitLab 的硬件要求。 步骤 更新系统包: sudo apt update sudo apt upg…...
网络知识小科普--5
81、什么是组播路由? 组播路由是一种有针对性的广播形式,将消息发送到所选择的用户组,而不是将其发送到子网上的所有用户。 82、加密在网络上的重要性是什么? 加密是将信息转换成用户不可读的代码的过程。然后使用秘密密钥或密码将其翻译或解密回其…...
JavaScript学习记录23
第十一节 JSON对象 1. JSON 格式 JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。 相比 XML 格式,JSON 格式有两个显…...
VScode 开发 Springboot 程序
1. 通过maven创建springboot程序 输入 mvn archetype:generate 选择模板,一般默认选择为第 7 种方式; 选择之后,一般要你填写如下内容: groupId: 组织名称;artifactId: 项目名称;version: 版本࿰…...
.git/hooks/post-merge 文件的作用
.git/hooks/post-merge 文件是 Git 版本控制系统中的一个钩子(hook)脚本,其作用是在合并(merge)操作完成后自动执行一些特定的操作。以下是关于 .git/hooks/post-merge 文件作用的详细解释: 作用 自动化任…...
Kafak 单例生产者实现-C#操作
前面写了一篇入门操作的文章,因为工作需要,简单修改了下如何实现单例生产者。 Kafka入门-C#操作_c# kafka-CSDN博客文章浏览阅读1.6k次,点赞20次,收藏9次。2).报错:“kafka.zookeeper.ZooKeeperClientTimeoutException: Timed out waiting for connection while in state…...
Cursor开发前端的详细过程
以下是使用 Cursor 开发前端的详细过程: 一、创建项目 打开 Cursor 并新建项目: 启动 Cursor 编辑器。点击 “File” 菜单,选择 “New Project”。在弹出的对话框中,输入项目名称,如 “MyFrontendProject”࿰…...
基于微信小程序的移动学习平台的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
atheris从安装到fuzz输入输出解读
1. 引入 模糊测试是一种自动化的软件测试技术,它通过自动生成大量随机数据作为输入来测试程序,以发现潜在的错误、漏洞或崩溃。atheris是一个专门用于CPython(Python的C语言实现)的模糊测试框架。 2. 安装atheris 参考1&#x…...
「 机器人 」系统辨识实验浅谈
前言 系统辨识实验是一种通过实验和数据分析的方法,用于建立物理系统的数学模型的技术。系统辨识是控制工程和系统科学中的重要环节,尤其是在模型未知或复杂的情况下。以下是系统辨识实验的详细介绍: 1. 系统辨识实验的目的 1.1 建模 为动态系统(如机械系统、电气系统或生…...
基于Flask的哔哩哔哩评论数据可视化分析系统的设计与实现
【Flask】基于Flask的哔哩哔哩评论数据可视化分析系统的设计与实现(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统可以搜索查看作者、播放量、评论等相关信息,并将相关的分析…...
[央企大赛 2025] pwn
拿到堆附件,不清楚哪个是密码哪个是pwn,找到两个pwn,一个RSA密码相对简单(已知e,d,N,直接用N解出k((ed-1)//phi_N(ed-1)//N^2),然后求pq,而phi_N正好是pq的2次方程)。就只复现了两个pwn,感觉还有…...
C语言初阶--折半查找算法
目录 练习1:在一个有序数组中查找具体的某个数字n 练习2:编写代码,演示多个字符从两端移动,向中间汇聚 练习3:简单编写代码实现,模拟用户登录情景,并且只能登录三次 练习4:猜数字…...
Python!从0开始学爬虫:(一)HTTP协议 及 请求与响应
前言 爬虫需要基础知识,HTTP协议只是个开始,除此之外还有很多,我们慢慢来记录。 今天的HTTP协议,会有助于我们更好的了解网络。 一、什么是HTTP协议 (1)定义 HTTP(超文本传输协议ÿ…...
[ Spring ] Spring Cloud Gateway 2025 Comprehensive Overview
文章目录 Spring Gateway ArchitectureProject Level DependencyService CenterService ProviderGateway ServiceLaunch All Service Spring Gateway Architecture Service Center : register and find service providerService Provider : programs that provide actual serv…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
【网络安全】开源系统getshell漏洞挖掘
审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...
