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, // 第一个子fiber
    sibling: null, // 下一个兄弟fiber
    return: returnFiber, // 父节点
    // 标记节点是什么类型的
    flags: Placement,
    deletions: null, // 要删除子节点 null或者[]
    index: null, //当前层级下的下标,从0开始
    // 记录上一次的状态 函数组件和类组件不一样
    memorizedState: null,
    // old fiber
    alternate: 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 a
    case 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结构的vnode
  const 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是个函数 直接执行拿到children
  const { 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===0
    if (previousNewFiber === null) {
      returnFiber.child = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }
    // 记录一下上次的fiber
    previousNewFiber = 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 方法来出触发组件更新
然后回到了上面初次渲染一样的逻辑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/983305.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AutoGen学习笔记系列(七)Tutorial - Managing State

这篇文章瞄准的是AutoGen框架官方教程中的 Tutorial 章节中的 Managing State 小节&#xff0c;主要介绍了如何对Team内的状态管理&#xff0c;特别是如何 保存 与 加载 状态&#xff0c;这对于Agent系统而言非常重要。 官网链接&#xff1a;https://microsoft.github.io/auto…

Compose Multiplatform+Kotlin Multiplatfrom 第四弹跨平台

文章目录 引言功能效果开发准备依赖使用gradle依赖库MVIFlow设计富文本显示 总结 引言 Compose Multiplatformkotlin Multiplatfrom 今天已经到compose v1.7.3&#xff0c;从界面UI框架上实战开发看&#xff0c;很多api都去掉实验性注解&#xff0c;表示稳定使用了&#xff01;…

[Java基础-线程篇]7_线程设计模式与总结

摘要&#xff1a;懒汉单例模式怎么变得线程安全&#xff1f;Master-Worker归并模式&#xff0c;工作窃取算法。Java线程相关源码使用了什么设计模式&#xff1f; 资料引用&#xff1a;《Java高并发核心编程卷2》 目录 线程安全的单例模式 Master-Worker模式 工作窃取算法 …

Kubermetes 部署mysql pod

步骤 1: 创建 PersistentVolume 和 PersistentVolumeClaim 首先为 MySQL 创建一个 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 来确保数据的持久性。 mysql-pv.yaml&#xff1a; apiVersion: v1 kind: PersistentVolume metadata:name: mysql-pv-volume spec:cap…

【四.RAG技术与应用】【12.阿里云百炼应用(下):RAG的云端优化与扩展】

在上一篇文章中,我们聊了如何通过阿里云百炼平台快速搭建一个RAG(检索增强生成)应用,实现文档智能问答、知识库管理等基础能力。今天咱们继续深入,聚焦两个核心问题:如何通过云端技术优化RAG的效果,以及如何扩展RAG的应用边界。文章会穿插实战案例,手把手带你踩坑避雷。…

交叉编译openssl及curl

操作环境&#xff1a;Ubuntu20.04 IDE工具&#xff1a;Clion2020.2 curl下载地址&#xff1a;https://curl.se/download/ openssl下载地址&#xff1a;https://openssl-library.org/source/old/index.html 直接交叉编译curl会报错找不到openssl&#xff0c;所以需要先交叉编…

在笔记本电脑上用DeepSeek搭建个人知识库

最近DeepSeek爆火&#xff0c;试用DeepSeek的企业和个人越来越多。最常见的应用场景就是知识库和知识问答。所以本人也试用了一下&#xff0c;在笔记本电脑上部署DeepSeek并使用开源工具搭建一套知识库&#xff0c;实现完全在本地环境下使用本地文档搭建个人知识库。操作过程共…

HarmonyOS 应用程序包结构 (发布态)

每个应用中至少包含一个.hap文件&#xff0c;可能包含若干个.hsp文件、也可能不含&#xff0c;一个应用中的所有.hap与.hsp文件合在一起称为Bundle&#xff0c;其对应的bundleName是应用的唯一标识&#xff08;详见app.json5配置文件中的bundleName标签&#xff09;。 当应用发…

idea中的查看git历史记录,不显示详细信息

一、正常情况显示 1、idea中git查看history正常显示如下图&#xff1a; 二、非正常情况下显示 1、idea中git查看history&#xff0c;现在不显示提交的历史文件详细信息&#xff0c;如下图&#xff1a; 三、解决方式 1、找到如下窗口中画红色框的黑色线条&#xff0c;鼠标放在…

江科大51单片机笔记【9】DS1302时钟可调时钟(下)

在写代码前&#xff0c;记得把上一节的跳线帽给插回去&#xff0c;不然LCD无法显示 一.DS1302时钟 1.编写DS1302.c文件 &#xff08;1&#xff09;重新对端口定义名字 sbit DS1302_SCLKP3^6; sbit DS1302_IOP3^4; sbit DS1302_CEP3^5;&#xff08;2&#xff09;初始化 因为…

数学建模笔记——层次分析法(AHP)

本文借鉴了数学建模清风老师的视频和课件,如有错误欢迎大家批评指正。原视频地址:清风数学建模:https://www.bilibili.com/video/BV1DW411s7wihttps://www.bilibili.com/video/BV1DW411s7wi 1.预备知识 层次分析法: 层次分析法(The Analytic Hierarchy Process,AHP)是一…

阿里云扩容操作步骤

在快照中备份服务器快照&#xff0c;时间为1天 进入块存储模块进行扩容 付款完成后进入账单进行查询&#xff0c;确认成功后找售后确认挂载盘情况 [rootatcoin ~]# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 1.8G 0 1.8G 0% /dev tmpfs…

【系统架构设计师】以数据为中心的体系结构风格

目录 1. 说明2. 仓库体系结构风格3. 黑板体系结构风格 1. 说明 1.以数据为中心的体系结构风格主要包括仓库体系结构风格和黑板体系结构风格。 2. 仓库体系结构风格 1.仓库&#xff08;Repository&#xff09;是存储和维护数据的中心场所。2.在仓库风格中&#xff0c;有两种不…

llamafactory大模型微调教程(周易大模型案例)

1.环境说明 操作系统&#xff1a;ubuntu 20 基础模型&#xff1a;Qwen2.5-1.5B-Instruct 工具&#xff1a;llamafactory GPU&#xff1a;四张4090 2、环境部署 2.1 下载基础模型 # 1、下载 modelscope pip install modelscope#2、模型下载 cd /data/ cat >> download…

go切片定义和初始化

1.简介 切片是数组的一个引用&#xff0c;因此切片是引用类型&#xff0c;在进行传递时&#xff0c;遵守引用传递的机制。切片的使用和数组类似&#xff0c;遍历切片、访问切片的元素和切片的长度都一样。。切片的长度是可以变化的&#xff0c;因此切片是一个可以动态变化的数…

2025年03月07日Github流行趋势

项目名称&#xff1a;ai-hedge-fund 项目地址url&#xff1a;https://github.com/virattt/ai-hedge-fund项目语言&#xff1a;Python历史star数&#xff1a;12788今日star数&#xff1a;975项目维护者&#xff1a;virattt, seungwonme, KittatamSaisaard, andorsk, arsaboo项目…

蓝桥杯每日一题:第一周周四哞叫时间

蓝桥杯每日一题&#xff1a;第一周周四哞叫时间 疑惑&#xff1a;如何把复杂度控制在Q&#xff08;n&#xff09;&#xff0c;怎么枚举a和b&#xff0c;longlong的形式又该怎么输入&#xff08;考虑用string&#xff09; 思路&#xff1a;枚举倒数第二个b前面有多少个a 这是一…

常见排序算法鉴赏(原理剖析+动图演示)

目录 一、冒泡排序&#xff08;BubbleSort&#xff09; 二、选择排序&#xff08; SelectSort&#xff09; 三、插入排序&#xff08;InsertSort&#xff09; 四、希尔排序&#xff08;ShellSort&#xff09; 五、堆排序 六、快排&#xff08;QuickSort&#xff09; Hoa…

易基因特异性R-loop检测整体研究方案

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 01.技术简述 R-loop是由DNA:RNA 杂交体和被置换的单链DNA组成的三链核酸结构&#xff0c;广泛参与基因转录、表观遗传调控及DNA修复等关键生物学过程。异常的R-loop积累会导致基因组不稳…

用低代码平台集成人工智能:无需专业开发也能实现智能化

引言&#xff1a;人工智能的普及与企业需求 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;越来越多的企业开始意识到其在提升运营效率、优化客户体验和推动业务创新方面的巨大潜力。从智能客服到自动化决策支持&#xff0c;从数据分析到个性化推荐&#x…