当前位置: 首页 > news >正文

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

  1. 为什么需要fiber

    对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验。

  2. 任务分解的意义

    解决上面的问题

  3. 增量渲染(把渲染任务拆分成块,匀到多帧)

  4. 更新时能够暂停,终止,复用渲染任务

  5. 给不同类型的更新赋予优先级

  6. 并发方面新的基础能力

  7. 更流畅

创建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();}
}
  1. 根据最原始的 vnode 节点(jsx) 调用 createFiber 方法生成我们需要的 fiber 结构的 vnode
    这一块已经实现了
const fiber = createFiber(element, {type: containerInfo.nodeName.toLocaleLowerCase(),stateNode: containerInfo,});
  1. 根据 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;}
}
  1. 处理完所有 fiber 和 子 fiber 后,开始往 root 节点里面进行递归提交,包括提交自己,第一个子节点,第一个子节点的兄弟节点(增删改查)的操作 调用了 commitRoot(commitWork)方法

  2. 根据 flags 属性来判断是新增 还是更新 还是删除

    1. 新增则调用 dom 元素的 appendChild 方法
    2. 更新则根据新老节点对比 调用 updateNode 方法
    3. 删除则调用 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);
}
  1. 初始化结束

更新(更新操作无非就是 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…...

LLM 大模型基础认知篇

目录 1、基本概述 2、大模型工作原理 3、关键知识点 &#xff08;1&#xff09;RAG 知识库 &#xff08;2&#xff09;蒸馏 &#xff08;3&#xff09;微调 &#xff08;4&#xff09;智能体 1、基本概述 大型语言模型&#xff08;Large Language Model, LLM&#xff09…...

leetcode700-二叉搜索树中的搜索

leetcode 700 思路 我们需要先了解一下二叉搜索树的特性&#xff1a; 左子树的所有节点值 < 当前节点的值。右子树的所有节点值 > 当前节点的值。这个特性适用于树中的每个节点 那么根据这个特性&#xff0c;我们可以通过根节点的值和目标值的大小来判断后序的走向&…...

《MySQL三大核心日志解析:Undo Log/Redo Log/Bin Log对比与实践指南》

MySQL三大核心日志解析&#xff1a;Undo Log/Redo Log/Bin Log对比与实践指南 一、核心日志全景概览 在MySQL数据库体系中&#xff0c;Undo Log、Redo Log和Bin Log构成了事务处理和数据安全的三大基石。这三大日志各司其职&#xff0c;协同保障了数据库的ACID特性与高可用架…...

java中实体类常见的设计模式

实体类常见的设计模式 1. Set 链式编程 在实体类中实现链式调用通常是指让 setter 方法返回当前对象实例&#xff08;this&#xff09;&#xff0c;从而允许连续调用多个 setter 方法设置属性值。这种方式可以使代码更加简洁和直观。 例如实体类为&#xff1a; public clas…...

【够用就好006】如何从零开发游戏上架steam面向AI编程的godot独立游戏制作实录001流程

记录工作实践 这是全新的系列&#xff0c;一直有个游戏制作梦 感谢AI时代&#xff0c;让这一切变得可行 长欢迎共同见证&#xff0c;期更新&#xff0c;欢迎保持关注&#xff0c;待到游戏上架那一天&#xff0c;一起玩 面向AI编程的godot独立游戏制作流程实录001 本期是第…...

发行思考:全球热销榜的频繁变动

几点杂感&#xff1a; 1、单机游戏销量与在线人数的衰退是剧烈的&#xff0c;有明显的周期性&#xff0c;而在线游戏则稳定很多。 如去年的某明星游戏&#xff0c;最高200多万在线&#xff0c;如今在线人数是48名&#xff0c;3万多。 而近期热门的是MH&#xff0c;在线人数8…...

docker目录挂载与卷映射的区别

在 Docker 中&#xff0c;目录挂载&#xff08;Bind Mount&#xff09;和卷映射&#xff08;Volume Mount&#xff09;的命令语法差异主要体现在路径格式上&#xff0c;具体表现为是否以斜杠&#xff08;/&#xff09;开头。以下是两者的核心区别及使用场景的总结&#xff1a; …...

`label` 标签的 `for` 属性详解

一、基本概念 label 标签的 for 属性用于将标签与表单控件&#xff08;如 input、select 等&#xff09;绑定&#xff0c;其值需与目标元素的 id 完全匹配。这种关联允许用户点击标签时触发控件交互&#xff08;如聚焦输入框或切换复选框&#xff09;&#xff0c;提升操作便捷…...

公开笔记:自然语言处理(NLP)中文文本预处理主流方法

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;将中文文本转化为数字的主流方法主要集中在预训练语言模型和子词编码技术上。这些方法能够更好地捕捉语义信息&#xff0c;并且在各种NLP任务中表现出色。以下是目前主流的文本编码方法&#xff1a; 1. 基于预训练语…...

【一个月备战蓝桥算法】递归与递推

字典序 在刷题和计算机科学领域&#xff0c;字典序&#xff08;Lexicographical order&#xff09;也称为词典序、字典顺序、字母序&#xff0c;是一种对序列元素进行排序的方式&#xff0c;它模仿了字典中单词的排序规则。下面从不同的数据类型来详细解释字典序&#xff1a; …...

算法策略深度解析与实战应用

一、算法策略的本质与价值 算法策略是计算机科学的灵魂&#xff0c;它决定了问题解决的效率与质量。优秀的算法设计者就像战场上的指挥官&#xff0c;需要根据地形&#xff08;问题特征&#xff09;选择最佳战术&#xff08;算法策略&#xff09;。本文将深入剖析五大核心算法…...

【LeetCode 热题 100】3. 无重复字符的最长子串 | python 【中等】

美美超过管解 题目&#xff1a; 3. 无重复字符的最长子串 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。 注…...

计算机网络(1) 网络通信基础,协议介绍,通信框架

网络结构模式 C/S-----客户端和服务器 B/S -----浏览器服务器 MAC地址 每一个网卡都拥有独一无二的48位串行号&#xff0c;也即MAC地址&#xff0c;也叫做物理地址、硬件地址或者是局域网地址 MAC地址表示为12个16进制数 如00-16-EA-AE-3C-40 &#xff08;每一个数可以用四个…...

在 Docker 中,无法直接将外部多个端口映射到容器内部的同一个端口

Docker 的端口映射是一对一的&#xff0c;即一个外部端口只能映射到容器内部的一个端口。 1. 为什么不能多对一映射&#xff1f; 端口冲突&#xff1a; 如果外部多个端口映射到容器内部的同一个端口&#xff0c;Docker 无法区分外部请求应该转发到哪个内部端口&#xff0c;会…...

计算机网络开发(2)TCP\UDP区别、TCP通信框架、服务端客户端通信实例

TCP与UDP区别 UDP&#xff1a;用户数据报协议&#xff0c;面向无连接&#xff0c;可以单播&#xff0c;多播&#xff0c;广播&#xff0c; 面向数据报&#xff0c;不可靠TCP&#xff1a;传输控制协议&#xff0c;面向连接的&#xff0c;可靠的&#xff0c;基于字节流&#xff…...

ubuntu打包 qt 程序,不用每次都用linuxdeployqt打包

用linuxdeployqt打包太麻烦&#xff0c;每次程序编译都要用linuxdeployqt打包一次&#xff0c;而且每次都要很长时间&#xff0c;通过研究得出一个新的打包方法 1.用用linuxdeployqt得出依赖的库文件&#xff08;只要没有增加新模块&#xff0c;只要用一次就可以&#xff09; …...

【Python项目】基于深度学习的车辆特征分析系统

【Python项目】基于深度学习的车辆特征分析系统 技术简介&#xff1a;采用Python技术、MySQL数据库、卷积神经网络&#xff08;CNN&#xff09;等实现。 系统简介&#xff1a;该系统基于深度学习技术&#xff0c;特别是卷积神经网络&#xff08;CNN&#xff09;&#xff0c;用…...

C++(初阶)(二)——类和对象

类和对象 类和对象类的定义格式访问限定符类域 实例化实例化概念内存对齐 this指针 类的定义 类&#xff08;Class&#xff09;是一种用于创建对象的蓝图或模板。它定义了对象&#xff08;变量&#xff09;的属性&#xff08;数据&#xff09;和方法&#xff08;行为&#xff…...

JS—组成:2分钟掌握什么是ECMAScript操作,什么是DOM操作,什么是BOM操作

个人博客&#xff1a;haichenyi.com。感谢关注 1. 目录 1–目录2–组成3–内置对象 2. 组成 一直都在说JS&#xff0c;JS&#xff0c;到底啥是JS有了解过吗&#xff1f;JS由哪几部分组成的呢&#xff1f; 定义&#xff1a; JavaScript是一种轻量级、解释型或即时编译型的编程语…...

如何通过Snap Hutao实现原神游戏决策的智能化?

如何通过Snap Hutao实现原神游戏决策的智能化&#xff1f; 【免费下载链接】Snap.Hutao 实用的开源多功能原神工具箱 &#x1f9f0; / Multifunctional Open-Source Genshin Impact Toolkit &#x1f9f0; 项目地址: https://gitcode.com/GitHub_Trending/sn/Snap.Hutao …...

为什么Python社区推荐用pipx替代pip?以virtualenv安装为例演示工作流

为什么Python开发者应该用pipx替代pip&#xff1f;以virtualenv为例的完整隔离方案 当你在Ubuntu终端输入pip install virtualenv时&#xff0c;那个刺眼的externally-managed-environment错误提示就像一堵墙——这不是技术故障&#xff0c;而是Python生态进化的重要路标。传统…...

【Python并发革命】:GIL解除后首个生产级无锁插件生态正式开放下载(限时72小时)

第一章&#xff1a;Python并发革命的里程碑意义 Python 并发模型的演进并非渐进式改良&#xff0c;而是一场深刻重塑编程范式的革命。从早期依赖线程与锁的阻塞式模型&#xff0c;到 asyncio 的异步 I/O 抽象、async/await 语法糖的引入&#xff0c;再到结构化并发&#xff08;…...

zynq7020 u-boot 外设配置实战指南

1. Zynq7020 U-Boot外设配置概述 在嵌入式系统开发中&#xff0c;U-Boot作为系统启动加载器扮演着关键角色。对于Xilinx Zynq-7020平台来说&#xff0c;正确配置U-Boot外设是确保系统正常启动和运行的基础。本文将重点介绍网口、QSPI Flash和eMMC这三个核心外设的配置方法。 为…...

35AE92 GJR5137200R0005电子模块

35AE92 GJR5137200R0005 电子模块是一款工业控制系统用的电子控制模块&#xff0c;通常用于西门子或ABB等自动化设备中&#xff0c;承担信号处理、控制逻辑执行及系统接口功能。开头&#xff1a;35AE92 GJR5137200R0005电子模块是工业自动化控制系统的重要组成部分&#xff0c;…...

如何用网盘直链下载助手突破限制提升效率:5个实用技巧

如何用网盘直链下载助手突破限制提升效率&#xff1a;5个实用技巧 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼…...

Qwen2-VL-2B-Instruct一键部署教程:基于Ubuntu 20.04的GPU环境快速搭建

Qwen2-VL-2B-Instruct一键部署教程&#xff1a;基于Ubuntu 20.04的GPU环境快速搭建 你是不是也遇到过这种情况&#xff1f;看到一个很酷的多模态大模型&#xff0c;想立刻上手试试&#xff0c;结果被复杂的依赖安装、环境配置、驱动适配搞得头大&#xff0c;折腾半天还没跑起来…...

突破资源封装壁垒:RePKG开源工具全维度应用指南

突破资源封装壁垒&#xff1a;RePKG开源工具全维度应用指南 【免费下载链接】repkg Wallpaper engine PKG extractor/TEX to image converter 项目地址: https://gitcode.com/gh_mirrors/re/repkg 问题&#xff1a;专用资源格式的困境与破局思路 如何突破专用格式的封锁…...

万象视界灵坛效果展示:血条式置信度进度条与‘同步率’动态分布图实录

万象视界灵坛效果展示&#xff1a;血条式置信度进度条与同步率动态分布图实录 1. 平台概览 万象视界灵坛&#xff08;Omni-Vision Sanctuary&#xff09;是一款基于OpenAI CLIP技术的高级多模态智能感知平台。不同于传统视觉识别工具的单调界面&#xff0c;它将复杂的"语…...

开源小模型也能干大事:MinerU图文理解实战教程

开源小模型也能干大事&#xff1a;MinerU图文理解实战教程 1. 项目简介 OpenDataLab MinerU 是一个专门针对文档理解设计的智能多模态模型&#xff0c;基于 OpenDataLab/MinerU2.5-1.2B 模型构建。虽然只有 1.2B 参数&#xff0c;但这个模型在文档解析方面表现出色&#xff0…...