react中的fiber和初次渲染
源码中定义了不同类型节点的枚举值
组件类型
- 文本节点
- HTML标签节点
- 函数组件
- 类组件
- 等等
src/react/packages/react-reconciler/src/ReactWorkTags.js
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
什么是fiber
A Fiber is work on a Component that needs to be done or was done. There can be more than one per component.
fiber是指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个。
// 比如一个函数组件FunctionComponent 里面是
<div className="border"><p>段落</p><button>按钮</button>
</div>
// 那最后的fiber结构
const fiber_ = {type: "div",props: {className: "border",},child: {// 第一个子节点type: "p",props: { children: "段落" },sibling: {// 下一个兄弟节点type: "button",props: { children: "按钮" },},},
};
fiber结构

为什么需要fiber
-
为什么需要fiber
对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验。
-
任务分解的意义
解决上面的问题
-
增量渲染(把渲染任务拆分成块,匀到多帧)
-
更新时能够暂停,终止,复用渲染任务
-
给不同类型的更新赋予优先级
-
并发方面新的基础能力
-
更流畅
创建fiber结构
fiber就是一个js对象来抽象vnode
function createFiber(vnode, returnFiber) {const fiber = {type: vnode.type,key: vnode.key,stateNode: null, // 原生标签时候指dom节点,类组件时候指的是实例props: vnode.props,child: null, // 第一个子fibersibling: null, // 下一个兄弟fiberreturn: returnFiber, // 父节点// 标记节点是什么类型的flags: Placement,deletions: null, // 要删除子节点 null或者[]index: null, //当前层级下的下标,从0开始// 记录上一次的状态 函数组件和类组件不一样memorizedState: null,// old fiberalternate: null,};const { type } = vnode;if (isStr(type)) {// 原生标签fiber.tag = HostComponent;} else if (isFn(type)) {// 函数组件或者是类组件fiber.tag = type.prototype.isComponent ? ClassComponent : FunctionComponent;} else if (isUndefined(type)) {fiber.tag = HostText;fiber.props = { children: vnode };} else {fiber.tag = Fragment;}return fiber;
}
深度优先遍历每个fiber
对不同的类型节点tag,都有对应的处理方法
function performUnitOfWork() {const { tag } = wip;switch (tag) {// 原生标签 比如div span button p acase HostComponent:updateHostComponent(wip);break;case FunctionComponent:updateFunctionComponent(wip);break;case ClassComponent:updateClassComponent(wip);break;case Fragment:updateFragmentComponent(wip);break;case HostText:updateHostTextComponent(wip);break;default:break;}if (wip.child) {wip = wip.child;return;}let next = wip;while (next) {if (next.sibling) {wip = next.sibling;return;}next = next.return;}wip = null;
}
初次渲染
在react项目中我们都是通过以下方法来初始化组件
ReactDOM.createRoot(document.getElementById("root")).render(jsx);
那我们就来实现一下该createRoot和render方法
源码中的render是挂载到了原型对象上
// react-dom
import createFiber from "./ReactFiber";
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";// 构造函数
function ReactDOMRoot(internalRoot) {this._internalRoot = internalRoot;
}ReactDOMRoot.prototype.render = function (children) {// 最原始的vnode节点(jsx) 我们需要的是fiber结构的vnodeconst root = this._internalRoot;// 原生dom节点console.log(root, "root");updateContainer(children, root);
};// 初次渲染 组件到g根dom节点上
function updateContainer(element, container) {const { containerInfo } = container;const fiber = createFiber(element, {type: containerInfo.nodeName.toLocaleLowerCase(),stateNode: containerInfo,});// 组件初次渲染scheduleUpdateOnFiber(fiber);
}
function createRoot(container) {const root = { containerInfo: container };return new ReactDOMRoot(root);
}// 一整个文件是ReactDOM, createRoot是ReactDOM上的一个方法
export default { createRoot };
scheduleUpdateOnFiber方法实现
触发任务调度方法,来执行fiber的生成performUnitOfWork和commit提交两个步骤
scheduleCallback是借助了MessageChannel方法来从最小堆中取优先级最高的任务来执行,此处暂时表示执行workLoop方法
// import scheduleCallback from '...todo'
export function scheduleUpdateOnFiber(fiber) {wip = fiber;wipRoot = fiber;scheduleCallback(workLoop);// scheduleCallback(() => {// console.log("scheduleCallback1");// });// scheduleCallback(() => {// console.log("scheduleCallback2");// });// scheduleCallback(() => {// console.log("scheduleCallback3");// });// scheduleCallback(() => {// console.log("scheduleCallbac4");// });
}function workLoop() {//协调while (wip) {performUnitOfWork();}//提交if (!wip && wipRoot) {commitWork();}
}
- 根据最原始的 vnode 节点(jsx) 调用 createFiber 方法生成我们需要的 fiber 结构的 vnode
这一块已经实现了
const fiber = createFiber(element, {type: containerInfo.nodeName.toLocaleLowerCase(),stateNode: containerInfo,});
- 根据 fiber 上不同 tag 属性调用不同的 fiber 渲染方法 该方法里面调用了 reconcileChildren 方法(协调 children 生成 fiber 链表) 递归生成 fiber 单链表结构
以函数组件为例:
export function updateFunctionComponent(wip) {renderWithHooks(wip);// 函数组件的type是个函数 直接执行拿到childrenconst { type, props } = wip;// 子节点const children = type(props);reconcileChildren(wip, children);
}
reconcileChildren方法就是协调,协调所有后代节点生成fiber单链表结构
// 协调children生成fiber链表
export function reconcileChildren(returnFiber, children) {const newChildren = isArray(children) ? children : [children];// old fiber头节点let oldFiber = returnFiber.alternate?.child;// 为啥去掉这句就不能渲染了 todo ...? 现在不会了 但是会出现两个相同的元素if (isStringOrNumber(children)) {return;}// 实现fiber的链表结构let previousNewFiber = null;let newIndex = 0;for (newIndex = 0; newIndex < newChildren.length; newIndex++) {const newChild = newChildren[newIndex];// 如果newChil为null,会在createFiber中报错if (newChild === null) {continue;}const newFiber = createFiber(newChild, returnFiber);const same = sameNode(newFiber, oldFiber);// 更新复用if (same) {Object.assign(newFiber, {stateNode: oldFiber.stateNode,alternate: oldFiber,flags: Update, // 默认是Placement 新增});}if (!same && oldFiber) {// 删除节点deleteChild(returnFiber, oldFiber);}// ?? todo...if (oldFiber) {oldFiber = oldFiber.sibling;}// 第一个子fiber 好比nexIndex===0if (previousNewFiber === null) {returnFiber.child = newFiber;} else {previousNewFiber.sibling = newFiber;}// 记录一下上次的fiberpreviousNewFiber = newFiber;}if (newIndex === newChildren.length) {deleteRemainingChildren(returnFiber, oldFiber);return;}
}
-
处理完所有 fiber 和 子 fiber 后,开始往 root 节点里面进行递归提交,包括提交自己,第一个子节点,第一个子节点的兄弟节点(增删改查)的操作 调用了 commitRoot(commitWork)方法
-
根据 flags 属性来判断是新增 还是更新 还是删除
- 新增则调用 dom 元素的 appendChild 方法
- 更新则根据新老节点对比 调用 updateNode 方法
- 删除则调用 commitDeletion 通过 removeChild(父 dom 和子 dom)来删除
function commitWork(wip) {if (!wip) {return false;}// 1.更新自己const { flags, stateNode, type } = wip;// 追加if (flags & Placement && stateNode) {// 函数组件prop.children的父级是函数组件名 再往上就是root根节点// const parentNode = wip.return.stateNode;const parentNode = getParentNode(wip.return);parentNode.appendChild(stateNode);}// 更新if (flags & Update && stateNode) {updateNode(stateNode, wip.alternate.props, wip.props);}// 删除if (wip.deletions) {// 通过父节点来删除commitDeletion(wip.deletions, stateNode || parentNode);}// 2.更新子节点commitWork(wip.child);// 3.更新兄弟节点commitWork(wip.sibling);
}
- 初始化结束
更新(更新操作无非就是 useState,useReducer 等改变了组件状态而导致更新)
所以在 hook 函数里 我们需要去调用 scheduleUpdateOnFiber 方法来出触发组件更新
然后回到了上面初次渲染一样的逻辑
相关文章:
react中的fiber和初次渲染
源码中定义了不同类型节点的枚举值 组件类型 文本节点HTML标签节点函数组件类组件等等 src/react/packages/react-reconciler/src/ReactWorkTags.js export const FunctionComponent 0; export const ClassComponent 1; export const IndeterminateComponent 2; // Befo…...
闭包+求解候选码+最小函数依赖集
一、闭包 直接上例题 简单明了 A的闭包ABC ABC的闭包ABCD ABCD的闭包ABCDE ABCDE的闭包ABCDEG 等于集合R的全集 所以A的闭包为ABCDEG AB的闭包为ABC 二、候选码 答案: 三、最小函数依赖集 求F的最小函数依赖集 去掉多余的 然后! 化为最简...
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之功能优化,添加表格空状态提示,带插图的空状态,Table7空状态2
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
Unity Shader 学习15:可交互式雪地流程
本质是 利用顶点变换实现的: 通过一个俯视整个场地的正交摄像机,根据绑定在移动物体身上的粒子系统,来获取物体移动过的位置,记录到一张RenderTexture上作为轨迹图,再通过这张图来对雪地做顶点变换。 1. 由于顶点变换需…...
工具介绍《netcat》
nc(netcat)是一款功能强大的网络工具,被称为“网络瑞士军刀”,支持TCP/UDP协议,广泛用于调试、数据传输、端口扫描、网络连接测试等场景。以下是其详细介绍: 一、核心功能 端口扫描 检测目标主机的端口开放…...
嵌入式开发之串行数据处理
前题 前面几篇文章写了关于嵌入式软件开发时,关于串行数据处理的一些相关内容,有兴趣的可以看看《嵌入式开发:软件架构、驱动开发与串行数据处理》、《嵌入式软件开发之生产关系模型》和《嵌入式开发之Modbus-RTU协议解析》相关的内容。从业十…...
Centos的ElasticSearch安装教程
由于我们是用于校园学习,所以最好是关闭防火墙 systemctl stop firewalld systemctl disable firewalld 个人喜欢安装在opt临时目录,大家可以随意 在opt目录下创建一个es-standonely-docker目录 mkdir es-standonely-docker 进入目录编辑yml文件 se…...
SyntaxError: Unexpected token ‘xxx‘
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...
Unity自定义区域UI滑动事件
自定义区域UI滑动事件 介绍制作1.创建一个Image2.创建脚本 总结 介绍 一提到滑动事件联想到有太多的插件了比如EastTouchBundle,今天想单纯通过UI去做一个滑动事件而不是基于Box2d或者Box去做滑动事件。 制作 1.创建一个Image 2.创建脚本 using UnityEngine; us…...
单链表封装 - 使用JavaScript封装
痛苦就是在蜕变吗 目录 链表:链表的特点:单链表:单链表的封装- JS封装: 单链表的应用:解决回文:解决击鼓传花:十进制进制转换其他进制: 链表: 链表就是一种物理存储单元…...
GET3D:从图像中学习的高质量3D纹理形状的生成模型
【摘要】 本文提出了GET3D,这是一种新的生成模型,能够生成具有任意拓扑结构的高质量3D纹理网格,可以直接被3D渲染引擎使用并在下游应用中立即使用。现有的3D生成模型要么缺乏几何细节,要么生成的网格拓扑受限,通常不支持纹理,或者在生成过程中使用神经渲染器,使得它们在…...
TypeError: Cannot convert object to primitive value
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...
【uniapp】图片添加canvas水印
目录 需求&背景实现地理位置添加水印 ios补充 需求&背景 需求:拍照后给图片添加水印, 水印包含经纬度、用户信息、公司logo等信息。 效果图: 方案:使用canvas添加水印。 具体实现:上传图片组件是项目里现有的ÿ…...
Flutter——最详细原生交互(MethodChannel、EventChannel、BasicMessageChannel)使用教程
MethodChannel(方法通道) 用途:实现 双向通信,用于调用原生平台提供的 API 并获取返回结果。 场景:适合一次性操作,如调用相机、获取设备信息等。 使用步骤: Flutter 端:通过 Meth…...
如何在PHP爬虫中处理异常情况的详细指南
一、常见的异常类型 在爬虫开发中,可能会遇到以下几种常见的异常情况: 网络请求失败:目标服务器不可用或网络连接问题。 页面结构变化:目标网站更新了HTML结构,导致选择器无法正确匹配。 反爬机制触发:请…...
贪吃蛇身匀速运动模型
通用运动模型 我们已知斜线为移动的距离 d d d, x x x轴总偏移量为 d x dx dx, y y y轴总偏移量为 d y dy dy,在一帧当中,我们也知道能走的距离为 m d md md。那么作为一般的运动模型,该如何确定我们进行移动的方向呢&…...
npm 执行安装报错
Fix the upstream dependency conflict, or retry this command with --force or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution. 原因 主要的原因是 npm7 以上的版本,新增了一个对等依赖的特性,在以…...
SPA单页面应用优化SEO
1.SSR服务端渲染 将组件或页面通过服务器生成html,再返回给浏览器,如nuxt.js或vue-server-renderer const Vue require(vue); const server require(express)(); const renderer require(vue-server-renderer).createRenderer();const vueApp new …...
笔记五:C语言编译链接
Faye:孤独让我们与我们所爱的人相处的每个瞬间都无比珍贵,让我们的回忆价值千金。它还驱使你去寻找那些你在我身边找不到的东西。 ---------《寻找天堂》 目录 一、编译和链接的介绍 1.1 程序的翻译环境和执行环境 1.1.1 翻译环境 1.1.2 运行环境 …...
【c语言概述、数据类型、运算符与表达式精选题】
c语言概述、数据类型、运算符与表达式精选题 一、易错题1.1🎄 c程序的执行1.2🎄 c程序的基本组成单元1.3🎄 c程序的组成1.4🎄 5种基本类型数据类型长度1.5🎄 C语言关键字1.6🎄 整型常量1.7🎄 合…...
200个前卫街头氛围涂鸦艺术水墨颜料手绘笔迹飞溅PNG免扣迭加纹理素材 VANTABLACK TEXTURES
探索 Vantablack 200 纹理包:您获得前卫、高分辨率纹理的首选资源。非常适合旨在为其作品添加原始都市氛围的设计师。这些透明迭加层使用简单,但非常有效,只需单击几下,即可将您的设计从普通变为非凡。准备好用既酷又百搭的质地来…...
机试准备第11天
第一题是浮点数加法,目前写过最长的代码。 #include <stdio.h> #include <string> #include <iostream> #include <vector> using namespace std; int main() {string str1;string str2;while (getline(cin, str1) && getline(cin…...
OpenIndiana Hipster系统安装配置
gcc安装 直接pkt install gcc会报错 需要 先pkt update,然后重启(不重启还是报错)用pkg search compiler找到可用的gcc包再pkt install xx安装这个包 TCP配置 参考这个网站:https://community.spiceworks.com/t/setting-tcp-p…...
深度学习模型Transformer核心组件—自注意力机制
第一章:人工智能之不同数据类型及其特点梳理 第二章:自然语言处理(NLP):文本向量化从文字到数字的原理 第三章:循环神经网络RNN:理解 RNN的工作机制与应用场景(附代码) 第四章:循环神经网络RNN、LSTM以及GR…...
Java核心语法:从变量到控制流
一、变量与数据类型(对比Python/C特性) 1. 变量声明三要素 // Java(强类型语言,需显式声明类型) int age 25; String name "CSDN"; // Python(动态类型) age 25 name …...
nature genetics | SCENT:单细胞多模态数据揭示组织特异性增强子基因图谱,并可识别致病等位基因
–https://doi.org/10.1038/s41588-024-01682-1 Tissue-specific enhancer–gene maps from multimodal single-cell data identify causal disease alleles 研究团队和单位 Alkes L. Price–Broad Institute of MIT and Harvard Soumya Raychaudhuri–Harvard Medical S…...
大白话如何使用 CSS 实现响应式布局?请列举一些常见的方法。
大白话如何使用 CSS 实现响应式布局?请列举一些常见的方法。 答题思路 首先要解释什么是响应式布局,让读者明白其概念和重要性。然后依次介绍常见的实现响应式布局的CSS方法,包括媒体查询、弹性布局(Flexbox)、网格布…...
基于数据挖掘的疾病数据可视化分析与预测系统
【大数据】基于数据挖掘的疾病数据可视化分析与预测系统(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 📌 技术核爆点:✔️ Python全栈开发Flask高能框架 ✔️ 爬虫技术…...
《AI大模型专家之路》No.2:用三个模型洞察大模型NLP的基础能力
用三个模型洞察大模型NLP的基础能力 一、项目概述 在这个基于AI构建AI的思维探索项目中,我们实现了一个基于BERT的中文AI助手系统。该系统集成了文本分类、命名实体识别和知识库管理等功能,深入了解本项目可以让读者充分了解AI大模型训练和推理的基本原…...
Spring Boot集成Minio笔记
一、首先配置MinIO 1、MinIO新建Bucket,访问控制台如图 创建访问密钥(就是账号和密码) 二、集成mino添加Minio客户端依赖 1.maven构建方式在pom.xml引入jar <dependency><groupId>io.minio</groupId><artifactId>minio</artifactI…...
