当前位置: 首页 > 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是一种轻量级、解释型或即时编译型的编程语…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日&#xff0c;2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席&#xff0c;并作《智能体在安全领域的应用实践》主题演讲&#xff0c;分享了在智能体在安全领域的突破性实践。他指出&#xff0c;百度通过将安全能力…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...