React的fiber原理

news2024/9/29 19:27:57

在读完这篇文章之后,大家可以回到文章开头再捋一下以下几个关键词,将React的 Fiber架构原理彻底搞清楚。
关键词:

  • requestIdleCallback、IdleDeadline
  • Fiber:React的一个执行单元

在Fiber 出现之前,React存在什么问题?

React 16 之前,更新 Virtual DOM 的过程是采用Stack架构(循环加递归)实现的,任务一旦开始就无法中断;
如果 Virtual DOM 层级较深,主线程被长期占用,直到整颗虚拟DOM树对比更新完成之后主线程才能被释放,主线程才能执行其他任务,这会导致一些用户交互、动画无法立即执行;用户体感:页面卡顿。

Stack 架构的简单实现

这里,实现一个获取 jsx,然后将 jsx 转换成 DOM,然后添加到页面中的过程中。

const jsx = (
  <div id="a1">
    <div id="b1">
      <div id="c1"></div>
      <div id="c2"></div>
    </div>
    <div id="b2"></div>
  </div>
)

function render(vdom, container) {
  // 创建元素
  const element = document.createElement(vdom.type)
  // 为元素添加属性
  Object.keys(vdom.props)
    .filter(propName => propName !== "children") // 过滤 children 属性
    .forEach(propName => (element[propName] = vdom.props[propName]))
  // 递归创建子元素
  if (Array.isArray(vdom.props.children)) {
    vdom.props.children.forEach(child => render(child, element))
  }
  // 将元素添加到页面中
  container.appendChild(element)
}

render(jsx, document.getElementById("root"))

jsx代码被转换成了真实的DOM添加到了页面中
在这里插入图片描述

Fiber 如何解决性能问题

  • 放弃递归调用,因为递归调用一旦开始,无法终止;采用循环来模拟递归,因为循环可以随时被中断;(由链表取代了树,将虚拟dom连接,使得组件渲染的工作分片,到时会主动让出渲染主线程)
  • 将大的渲染任务拆分成一个个小任务(小任务指的是 Fiber节点的构建);
  • 使用 requestIdleCallback 去利用浏览器的空闲时间去执行小任务,React 在执行一个任务单元后,查看是否有其他高优先级的任务;如果有,放弃占用线程,先执行优先级高的任务。

fiber 这种数据结构使得节点可以回溯到其父节点、只要保留下中断的节点索引,就可以恢复之前的工作进度;

这里,简单解释一下 requestIdleCallback

window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。

requestIdleCallback 的作用:

浏览器的页面都是通过引擎一帧一帧绘制出来的,当每秒绘制的帧数达到 60 的时候,页面就是流畅的,玩过 fps 游戏的都知道,当这个帧数小于 60 的时候,人的肉眼就能感知出来卡顿。一秒 60 帧,每一帧分到的时间就是 1000/60 ≈ 16 ms,如果每一帧执行的时间小于 16 ms,就说明浏览器有空余时间,那么能不能通过浏览器的空余时间去处理任务呢,这样就不用一直等待主任务执行完了,requestIdleCallback 就是利用浏览器的空余时间去执行任务的。

requestIdleCallback 详解

Fiber 原理分析

Fiber 是一种数据结构,支撑 Fiber 构建任务的运转;
Fiber其实就是JavaScript对象(这个对象中有child属性表示节点的子节点,有sibling属性表示节点的下一个兄弟节点,有return属性表示节点的父级节点

那么当一个 Fiber 任务执行完成后,通过 链表结构找到下一个要执行的任务单元。

// 简易版 Fiber 对象
type Fiber = {
  // 组件类型 div、span、组件构造函数
  type: any,
  // DOM 对象
  stateNode: any,  
  // 指向自己的父级 Fiber 对象
  return: Fiber | null,
  // 指向自己的第一个子级 Fiber 对象
  child: Fiber | null,
  // 指向自己的下一个兄弟 Fiber 对象
  sibling: Fiber | null,
}

Fiber 工作共分为两个阶段:render阶段和commit阶段;

  • render阶段:构建Fiber对象,构建链表,在链表中标记要执行的DOM操作,可中断;
  • commit阶段:根据构建好的链表进行DOM操作,不可中断;

Fiber 的主要工作流程:

  • ReactDOM.render() 引导 React 启动或调用 setState() 的时候开始创建或更新 Fiber 树。

  • 从根节点开始遍历 Fiber Node Tree, 并且构建 WokeInProgress Tree(reconciliation 阶段)。

    • 本阶段可以暂停、终止、和重启,会导致 react 相关生命周期重复执行。
    • React 会生成两棵树,一棵是代表当前状态的 current tree,一棵是待更新的 workInProgress tree。
    • 遍历 current tree,重用或更新 Fiber Node 到 workInProgress tree,workInProgress tree 完成后会替换 current tree。
    • 每更新一个节点,同时生成该节点对应的 Effect List。
    • 为每个节点创建更新任务。
  • 将创建的更新任务加入任务队列,等待调度。

    • 调度由 scheduler 模块完成,其核心职责是执行回调。
    • scheduler 模块实现了跨平台兼容的 requestIdleCallback。
    • 每处理完一个 Fiber Node 的更新,可以中断、挂起,或恢复。
  • 根据 Effect List 更新 DOM (commit 阶段)。

    • React 会遍历 Effect List 将所有变更一次性更新到 DOM 上。
    • 这一阶段的工作会导致用户可见的变化。因此该过程不可中断,必须一直执行直到更新完成。
      在这里插入图片描述
const jsx = (
  <div id="a1">
    <div id="b1">
      <div id="c1"></div>
      <div id="c2"></div>
    </div>
    <div id="b2"></div>
  </div>
)

const container = document.getElementById("root")

// 构建根元素的 Fiber 对象
const workInProgressRoot = {
  stateNode: container,
  props: {
    children: [jsx]
  }
}
// 下一个要执行的任务
let nextUnitOfWork = workInProgressRoot

function workLoop(deadline) {
  // 1. 是否有空余时间
  // 2. 是否有要执行的任务
  while (nextUnitOfWork && deadline.timeRemaining() > 0) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
  // 表示所有的任务都已经执行完成了
  if (!nextUnitOfWork) {
    // 进入到第二阶段 执行DOM
    commitRoot()
  }
  requestIdleCallback(workLoop)
}

function commitRoot() {
  let currentFiber = workInProgressRoot.firstEffect
  while (currentFiber) {
    currentFiber.return.stateNode.appendChild(currentFiber.stateNode)
    currentFiber = currentFiber.nextEffect
  }
}

// 在浏览器空闲的时候执行任务
requestIdleCallback(workLoop)

function performUnitOfWork(workInProgressFiber) {
  // 1. 创建 DOM 对象并将它存储在 stateNode 属性
  // 2. 构建当前 Fiber 的子级 Fiber
  // 向下走的过程

  // 构建子集
  beginWork(workInProgressFiber);

  // 如果当前Fiber有子级
  if (workInProgressFiber.child) {
    // 返回子级 构建子级的子级
    return workInProgressFiber.child
  }

  while (workInProgressFiber) {
    // 向上走,构建链表
    completeUnitOfWork(workInProgressFiber)

    // 如果有同级
    if (workInProgressFiber.sibling) {
      // 返回同级 构建同级的子级
      return workInProgressFiber.sibling
    }
    // 更新父级
    workInProgressFiber = workInProgressFiber.return
  }
}
// 构建子集
function beginWork(workInProgressFiber) {
  // 1. 创建 DOM 对象并将它存储在 stateNode 属性
  if (!workInProgressFiber.stateNode) {
    // 创建 DOM
    workInProgressFiber.stateNode = document.createElement(
      workInProgressFiber.type
    )
    // 为 DOM 添加属性
    for (let attr in workInProgressFiber.props) {
      if (attr !== "children") {
        workInProgressFiber.stateNode[attr] = workInProgressFiber.props[attr]
      }
    }
  }

  // 2. 构建当前 Fiber 的子级 Fiber
  if (Array.isArray(workInProgressFiber.props.children)) {
    let previousFiber = null
    workInProgressFiber.props.children.forEach((child, index) => {
      let childFiber = {
        type: child.type,
        props: child.props,
        effectTag: "PLACEMENT",
        return: workInProgressFiber
      }
      if (index === 0) {
        // 构建子集,只有第一个子元素是子集
        workInProgressFiber.child = childFiber
      } else {
        // 不是第一个,则构建子集的 兄弟级
        previousFiber.sibling = childFiber
      }
      previousFiber = childFiber
    })
  }
  // console.log(workInProgressFiber)
}

function completeUnitOfWork(workInProgressFiber) {
  // 获取当前 Fiber 的父级
  const returnFiber = workInProgressFiber.return
  // 父级是否存在
  if (returnFiber) {
    // 需要执行 DOM 操作的 Fiber
    if (workInProgressFiber.effectTag) {
      if (!returnFiber.lastEffect) {
        returnFiber.lastEffect = workInProgressFiber.lastEffect
      }

      if (!returnFiber.firstEffect) {
        returnFiber.firstEffect = workInProgressFiber.firstEffect
      }

      if (returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = workInProgressFiber
      } else {
        returnFiber.firstEffect = workInProgressFiber
      }
      returnFiber.lastEffect = workInProgressFiber
    }
  }
}

为什么 vue 不需要 fiber 架构?

react 直到哪个组件触发了更新,但是不知道哪些子组件会受到影响。因此react 需要生成该组件下的所有虚拟DOM结构,与原本的虚拟DOM结构进行对比,找出变动的部分。

在 vue 中,一切影响页面内容的数据都应该是响应式的, vue通过拦截响应式数据的修改,知道哪些组件应该被修改,不需要遍历所有子树。vue的diff算法是对组件内部的diff,如果存在子组件,会判断子组件上与渲染相关的属性是否发生变化,无需变化的化则复用原本的DOM,不会处理子组件。

模板语法让vue能够进行更好地编译时分析,提高优化过程的效率,react缺少这部分,无法识别哪些是静态节点,哪些是动态节点。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

bxCAN总线Loopback功能实现(STM32F4xx)

目录 概述 1 认识bxCAN Loopback 1.1 环回模式 1.2 环回模式特点 2 使用STM32CubeMX 生成工程 2.1 参数配置 2.2 生成工程代码 2.4 了解can.c 3 认识Hal库中的bxCAN 3.1 认识3个重要数据结构 3.2 函数组 3.2.1 初始化函数组 3.2.2 控制函数组 3.2.3 中断管理函数…

【风格迁移】pix2pixHD:高分辨率图像生成

pix2pixHD&#xff1a;高分辨率图像生成 提出背景问题1: 如何提高生成图像的照片级真实感和分辨率&#xff1f;问题2: 如何利用实例级别的对象语义信息进一步提高图像质量&#xff1f;问题3: 如何实现图像合成的多模态性&#xff0c;允许交互式对象编辑&#xff1f; pix2pixHD …

【STK】手把手教你利用STK进行仿真-STK软件基础01 STK的对象组织模式

STK系统采用面向对象和分级组织结构的管理模式对仿真对象进行管理&#xff0c;这种组织二行管理模式非常符合人类的认知习惯&#xff0c;易于理解。STK最基本的对象为场景&#xff08;Scenario&#xff09;&#xff0c;在场景中可以面向对象和分级组织模式对各场景中的仿真对象…

[c++] c++ 中的顺序(构造,析构,初始化列表,继承)

对象构造的时候&#xff0c;对象成员变量的初始化顺序是什么样的 &#xff1f; 派生类构造的时候&#xff0c;先构造基类还是先构造派生类 &#xff1f; 构造函数中的初始化列表&#xff0c;初始化的顺序是列表的顺序吗 &#xff1f; 析构的时候&#xff0c;析构的顺序是什么…

华为数通方向HCIP-DataCom H12-821题库(多选题:41-60)

第41题 BGP OPEN消息中携带如下哪些信息? A、路由属性 B、BGP Router ID C、Hold time D、本地自治系统(AS)号 【参考答案】BCD 【答案解析】 B. BGP Router ID:OPEN消息中包含发送方BGP路由器的Router ID,用于唯一标识BGP路由器。C.Hold time:OPEN消息中包含发送方BGP路由…

区块链媒体发布推广10个热门案例解析-华媒舍

区块链技术的发展已经引起了媒体的广泛关注&#xff0c;越来越多的区块链媒体纷纷发布推广相关的热门案例。本文将介绍10个成功的区块链媒体推广案例&#xff0c;并分享它们的成功秘诀&#xff0c;帮助读者更好地了解区块链媒体推广的方法与技巧。 随着区块链技术的成熟和应用场…

vue2本地开发环境正常,生产环境下this.$router.push({ name: ‘login‘ })不跳转

如果在Vue.js 2中在本地开发环境下正常运行,但在生产环境下使用​​this.$router.push({ name: login })​​不起作用,可能有几个原因需要检查和解决: 路由配置问题: 确保你的路由配置正确,特别是确保在生产环境中,路由的配置和本地开发环境一致。检查是否正确设置了name…

课程表系列(BFS)

广度优先搜索 文章目录 广度优先搜索207. 课程表210. 课程表 II思路 630. 课程表 III1462. 课程表 IV547. 省份数量 207. 课程表 207. 课程表 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程…

多元函数积分思路合集

[曲线积分笔记]第一类曲线积分 [微积分笔记]第二类曲线/面积分总结 积分方法意义/提示平面第二型曲线积分化为定积分 ∮ L → ∫ a b \oint_L \to \int_a^b ∮L​→∫ab​平面第二型曲线积分格林公式 ∮ L → ∬ D \oint_L \to \iint_D ∮L​→∬D​化为对平面区域的二重积分空…

百度搜索引擎SEO优化方法

随着互联网的不断发展&#xff0c;搜索引擎已经成为人们获取信息、产品和服务的主要途径之一。而在中国&#xff0c;百度作为最大的搜索引擎&#xff0c;其影响力不可忽视。了解并掌握百度SEO关键词优化方法&#xff0c;对于提升网站在搜索引擎中的排名至关重要。 关键词选择&a…

chatgpt-3的文章生成器有哪些?可以批量生成文章的生成器

GPT-3&#xff08;Generative Pre-trained Transformer 3&#xff09;作为人工智能领域的一项重大突破&#xff0c;开启了新一代的文本生成技术。同时市面上也涌现出了一些GPT-3文章生成器&#xff0c;为用户提供了快速、高效地生成各种类型文章的工具。本文将介绍一些中国的GP…

JS:原型与原型链(附带图解与代码)

一、原型 写在前面&#xff1a; 任何对象都有原型。 函数也是对象&#xff0c;所以函数也有原型。 1.什么是原型 在 JavaScript 中&#xff0c;对象有一个特殊的隐藏属性 [[Prototype]]&#xff0c;它要么为 null&#xff0c;要么就是对另一个对象的引用&#xff0c;该对象…

MATLAB基于隐马尔可夫模型-高斯混合模型-期望最大化的MR图像分割

隐马尔可夫模型是一种统计模型&#xff0c;它描述了马尔可夫过程&#xff0c;隐马尔可夫过程中包含隐变量&#xff0c;语音识别和词性自动标注等一些领域常常使用隐马尔可夫模型方法来处理。马尔可夫过程是一类随机过程&#xff0c;马尔可夫链是它的原始模型&#xff0c;马尔可…

《OpenScene: 3D Scene Understanding with Open Vocabularies》阅读笔记1

传统的3D场景理解方法依赖于带标签的3D数据集,用于训练一个模型以进行单一任务的监督学习。我们提出了OpenScene,一种替代方法,其中模型在CLIP特征空间中预测与文本和图像像素共同嵌入的3D场景点的密集特征。这种零样本方法实现了与任务无关的训练和开放词汇查询。例如,为了…

03-JNI 类型和数据结构

上一篇&#xff1a; 02-设计概述 本章讨论 JNI 如何将 Java 类型映射为本地 C 类型。 3.1 原始类型 下表描述了 Java 原始类型及其与机器相关的本地等价类型。 为方便起见&#xff0c;定义如下&#xff1a; #define JNI_FALSE 0 #define JNI_TRUE 1 jsize 整数类型用于描述…

《Spring Security 简易速速上手小册》第8章 常见问题与解决方案(2024 最新版)

文章目录 8.1 异常处理和日志记录8.1.1 基础知识详解8.1.2 重点案例&#xff1a;统一异常处理案例 Demo拓展 8.1.3 拓展案例 1&#xff1a;日志记录策略案例 Demo拓展 8.1.4 拓展案例 2&#xff1a;日志聚合案例 Demo拓展 8.2 多租户安全性问题8.2.1 基础知识详解8.2.2 重点案例…

Redis之十:Spring Data Redis --- CrudRepository方式

SpringData Redis CrudRepository方式 Spring Data Redis 的 CrudRepository 是 Spring Data 框架中用于提供基础 CRUD&#xff08;创建、读取、更新和删除&#xff09;操作的一个接口。在与 Redis 集成时&#xff0c;尽管 Redis 是一个键值存储系统&#xff0c;并没有像关系型…

STM32使用FlyMcu串口下载程序与STLink Utility下载程序

文章目录 前言软件链接一、FlyMcu串口下载程序原理优化手动修改跳线帽选项字节其他功能 二、STLink Utility下载程序下载程序选项字节固件更新 前言 本文主要讲解使用FlyMcu配合USART串口为STM32下载程序、使用STLink Utility配合STLink为STM32下载程序&#xff0c;以及这两个…

javascript中对包含关系判断介绍

本文将为您详细讲解 JavaScript 中对包含关系的判断&#xff0c;包括数组、字符串等&#xff0c;并提供相应的代码例子。 1. 数组包含关系判断 在 JavaScript 中&#xff0c;数组包含关系判断通常使用 Array.prototype.includes() 方法。这个方法返回一个布尔值&#xff0c;表示…

C语言第三十四弹---动态内存管理(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 动态内存管理 1、动态内存经典笔试题分析 1.1、题目1 1.2、题目2 1.3、题目3 1.4、题目4 2、柔性数组 2.1、柔性数组的特点 2.2、柔性数组的使用 2.3、…