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

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...