面试官:你是怎样理解Fiber的

news2024/12/26 13:30:50

hello,这里是潇晨,今天我们来聊一聊Fiber。不知道大家面试的时候有没有遇到过和react Fiber相关的问题呢,这一类问题比较开放,但也是考察对react源码理解深度的问题,如果面试高级前端岗,恰巧你平时用的是react,那这道面试题是你必需要会的一道。

大型应用为什么会慢

那之前的应用为什么会慢呢,传统的前端应用例如js原生或者jquery应用,在构建复杂的大型应用的时候,各种页面之前的相互操作和更新很有可能会引起页面的重绘或重排列,而频繁操作这些dom其实是非常消耗性能的

react源码5.1

在看下图,这是一个节点上的属性,可以看到一个节点上的属性是非常多的,在复杂应用中,操作这些属性的时候可能一不小心就会引起节点大量的更新,那如何提高应用的性能呢?

const div = document.createElement('div');
let str = ''
for(let k in div){
  str+=','+k
}
console.log(str)

react源码5.2

为什么会出现Fiber

react从15版本开始,到现在的17,以及快出来的18,内部经历了非常大的变化,这一切都是围绕着一个目标进行的,这个目标是异步可中断的更新,而这个目的的最终结果是为了构建快速响应的应用。

复杂应用在更新的时候可能会更新大量的dom,所以react在应用层和dom层之间增加了一层Fiber,而Fiber是在内存中工作的,所以在更新的时候只需要在内存中进行dom更新的比较,最后再应用到需要更新真实节点上

这就引出了一个对比新老节点的过程,而对比两棵树的计算其实是非常消耗性能的,react提出了diff算法来降低对比的复杂度,具体diff的过程可以参考往期文章 diff算法

但是面对越来越复杂的应用,diff算法消耗的时间片还是很长,在没做出优化的情况下,react在进行Fiber的对比和更新节点上的状态的时候依然力不从心,

  • 在react15之前,这个对比的过程被称之为stack reconcile,它的对比方式是‘一条路走到黑’,也就是说这个对比的过程是不能被中断的,这会出现什么情况呢,比如在页面渲染一个比较消耗性能操作,如果这个时候如果用户进行一些操作就会出现卡顿,应用就会显得不流畅。
  • react16之后出现了scheduler,以及react17的Lane模型,它们可以配合着工作,将比较耗时的任务按照Fiber节点划分成工作单元,并且遍历Fiber树计算或者更新节点上的状态可以被中断、继续,以及可以被高优先级的任务打断,比如用户触发的更新就是一个高优先级的任务,高优先级的任务优先执行,应用就不会太卡顿。

什么是Fiber

这就是react所要做的事情了,react创新的提出了jsx,声明式地描述页面呈现的效果,jsx会被babel经过ast解析成React.createElement,而React.createElement函数执行之后就是jsx对象或者说是virtual-dom

  • 在mount的时候,也就是首次渲染的时候,render阶段会根据jsx对象生成新的Fiber节点,然后这些Fiber节点会被标记成带有‘Placement’的副作用,说明它们是新增的节点,需要被插入到真实节点中,在commit阶段就会操作真实节点,将它们插入到dom树中。
  • 在update的时候,也就是应用触发更新的时候,render阶段会根据最新的jsx和老的Fiber进行对比,生成新的Fiber,这些Fiber会带有各种副作用,比如‘Deletion’、‘Update’、‘Placement’等,这一个对比的过程就是diff算法 ,在commit阶段会操作真实节点,执行相应的副作用。

如果对render阶段和commit阶段不了解的可以查看往期文章

8.render阶段

10.commit阶段

react源码3.1

Fiber有比较多的含义,他可以从以下几个角度理解:

  • 工作单元 任务分解 :Fiber最重要的功能就是作为工作单元,保存原生节点或者组件节点对应信息(包括优先级),这些节点通过指针的形似形成Fiber树

  • 增量渲染:通过jsx对象和current Fiber的对比,生成最小的差异补丁,应用到真实节点上

  • 根据优先级暂停、继续、排列优先级:Fiber节点上保存了优先级,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力,也为上层实现批量更新、Suspense提供了基础

  • **保存状态:**因为Fiber能保存状态和更新的信息,所以就能实现函数组件的状态更新,也就是hooks

    相关参考视频讲解:进入学习

Fiber的数据结构

Fiber的自带的属性如下:

//ReactFiber.old.js
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  //作为静态的数据结构 保存节点的信息 
  this.tag = tag;//对应组件的类型
  this.key = key;//key属性
  this.elementType = null;//元素类型
  this.type = null;//func或者class
  this.stateNode = null;//真实dom节点

  //作为fiber数架构 连接成fiber树
  this.return = null;//指向父节点
  this.child = null;//指向child
  this.sibling = null;//指向兄弟节点
  this.index = 0;

  this.ref = null;

  //用作为工作单元 来计算state
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

    //effect相关
  this.effectTag = NoEffect;
  this.nextEffect = null;
  this.firstEffect = null;
  this.lastEffect = null;

  //优先级相关的属性
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  //current和workInProgress的指针
  this.alternate = null;
}

Fiber是怎样工作的

现在我们知道了Fiber可以保存真实的dom,真实dom对应在内存中的Fiber节点会形成Fiber树,这颗Fiber树在react中叫current Fiber,也就是当前dom树对应的Fiber树,而正在构建Fiber树叫workInProgress Fiber,这两颗树的节点通过alternate相连.

function App() {
  return (
        <>
      <h1>
        <p>count</p> xiaochen
      </h1>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById("root"));

react源码7.2

构建workInProgress Fiber发生在createWorkInProgress中,它能创建或者服用Fiber

//ReactFiber.old.js
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {//区分是在mount时还是在update时
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;//复用属性
    workInProgress.type = current.type;
    workInProgress.flags = NoFlags;

    workInProgress.nextEffect = null;
    workInProgress.firstEffect = null;
    workInProgress.lastEffect = null;

    //...
  }

  workInProgress.childLanes = current.childLanes;//复用属性
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
          lanes: currentDependencies.lanes,
          firstContext: currentDependencies.firstContext,
        };

  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;


  return workInProgress;
}
  • 在mount时:会创建fiberRoot和rootFiber,然后根据jsx对象创建Fiber节点,节点连接成current Fiber树。
    react源码7.1

  • 在update时:会根据新的状态形成的jsx(ClassComponent的render或者FuncComponent的返回值)和current Fiber对比形(diff算法)成一颗叫workInProgress的Fiber树,然后将fiberRoot的current指向workInProgress树,此时workInProgress就变成了current Fiber。fiberRoot:指整个应用的根节点,只存在一个

    fiberRoot:指整个应用的根节点,只存在一个

    rootFiber:ReactDOM.render或者ReactDOM.unstable_createRoot创建出来的应用的节点,可以存在多个。

    我们现在知道了存在current Fiber和workInProgress Fiber两颗Fiber树,Fiber双缓存指的就是,在经过reconcile(diff)形成了新的workInProgress Fiber然后将workInProgress Fiber切换成current Fiber应用到真实dom中,存在双Fiber的好处是在内存中形成视图的描述,在最后应用到dom中,减少了对dom的操作。

现在来看看Fiber双缓存创建的过程图

  • mount时:

    1. 刚开始只创建了fiberRoot和rootFiber两个节点
      react源码7.6
    2. 然后根据jsx创建workInProgress Fiber:
      react源码7.7
    3. 把workInProgress Fiber切换成current Fiber
      react源码7.8
  • update时

    1. 根据current Fiber创建workInProgress Fiber
      react源码7.9
    2. 把workInProgress Fiber切换成current Fiber

react源码7.8

为什么Fiber能提升效率

Fiber是一个js对象,能承载节点信息、优先级、updateQueue,同时它还是一个工作单元。

  1. Fiber双缓存可以在构建好wip Fiber树之后切换成current Fiber,内存中直接一次性切换,提高了性能
  2. Fiber的存在使异步可中断的更新成为了可能,作为工作单元,可以在时间片内执行工作,没时间了交还执行权给浏览器,下次时间片继续执行之前暂停之后返回的Fiber
  3. Fiber可以在reconcile的时候进行相应的diff更新,让最后的更新应用在真实节点上

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

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

相关文章

细谈JavaWeb中的Request和Response

文章目录1&#xff0c;Request和Response的概述2&#xff0c;Request对象2.1 Request继承体系2.2 Request获取请求数据2.2.1 获取请求行数据2.2.2 获取请求头数据2.2.3 获取请求体数据2.2.4 获取请求参数的通用方式2.4 请求参数中文乱码问题2.4.1 POST请求解决方案2.4.2 GET请求…

MySQL使用C语言连接

文章目录MySQL使用C语言连接引入库下载库文件在项目中使用库使用库连接数据库下发SQL请求获取查询结果MySQL使用C语言连接 引入库 要使用C语言连接MySQL&#xff0c;需要使用MySQL官网提供的库。 下载库文件 下载库文件 首先&#xff0c;进入MySQL官网&#xff0c;选择DEVEL…

Java代码使用最小二乘法实现线性回归预测

最小二乘法简介最小二乘法是一种在误差估计、不确定度、系统辨识及预测、预报等数据处理诸多学科领域得到广泛应用的数学工具。它通过最小化误差&#xff08;真实目标对象与拟合目标对象的差&#xff09;的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数…

如何写一个 things3 client

Things3[1] 是一款苹果生态内的任务管理软件&#xff0c;是一家德国公司做的&#xff0c;非常好用。我前后尝试了众多任务管理软件&#xff0c;最终选定 things3&#xff0c;以后有机会会写文章介绍我是如何用 things3 来管理我的日常任务。本文主要介绍欧神写的 tli[2] 工具来…

3D沉浸式体验开发技巧【Three.js】

在本文中&#xff0c;我们将看看如何使用 Three.js 创建一个充满后期效果和微交互的迷你城市。 推荐&#xff1a;将 NSDT场景编辑器 加入你的3D开发工具链。 1、背景介绍 我是一个游戏爱好者。 我一直梦想创建一个交互式迷你城市&#xff0c;使用饱和的颜色&#xff0c;类似于…

Android自动化测试(UiAutomator)——UiObject

本文主要讲解使用UiAutomator的一些技巧&#xff0c;希望对于初学者有一定的帮助 UiObject 1、首先要声明对象 UiObject XXX new UiObject(new Selector) ; 2、其次对对象进行操作 操作类型包括&#xff1a; 执行类&#xff1a;文本输入与清除、点击/长按、拖动/滑动、 …

JAVA JDK 常用工具类和工具方法

目录 Pair与Triple Lists.partition-将一个大集合分成若干 List集合操作的轮子 对象工具Objects 与ObjectUtils 字符串工具 MapUtils Assert断言 switch语句 三目表达式 IOUtils MultiValueMap MultiMap JAVA各个时间类型的转换&#xff08;LocalDate与Date类型&a…

开源软件AirByte:入湖入仓,数据集成管道

从ETL到ELT就传统的 ETL而言&#xff0c;当我们开始构建数据仓库时&#xff0c;都要先去了解业务流程&#xff0c;明晰业务是如何运转的&#xff0c;数据是如何留痕的。通过收集用户的相关需求&#xff0c;从而去规划设计报表。企业需要进行数仓分域、分层、逻辑建模等一系列操…

Linux下程序调试的方法【GDB】GDB相关命令和基础操作(命令收藏)

目录 1、编译 2、启动gdb调试 2.1 直接运行 2.2 运行gdb后使用run命令 2.3 调试已运行的程序 3、图形界面提示 4、调试命令 1、查看源码 2、运⾏程序/查看运⾏信息 3、设置断点 5、单步/跳步执⾏ 6、分割窗口 7、其他命令 8、相关参数 1、编译 在编译时要加上-g选…

stm32f407探索者开发板(十七)——串口寄存器库函数配置方法

文章目录一、STM32串口常用寄存器和库函数1.1 常用的串口寄存器1.2 串口相关的库函数1.3 状态寄存器&#xff08;USART_ SR&#xff09;1.4 数据寄存器&#xff08;USART_ DR&#xff09;1.5 波特率寄存器&#xff08;USART_BRR&#xff09;二、串口配置一般步骤一、STM32串口常…

java static关键字 万字详解

目录 一、为什么需要static关键字&#xff1a; 二、static关键字概述 : 1.作用 : 2.使用 : 三、static修饰成员变量详解 : 1.特点 : 2.细节 : ①什么时候考虑使用static关键字? ②静态变量和非静态变量的区别&#xff1f; ③关于静态变量的初始化问题 : ④关于静态变…

基于springboot+vue的器官捐献系统

基于springbootvue的器官捐献系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&…

内网渗透(四十一)之横向移动篇-PsExec工具远程命令执行横向移动

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

CCNP350-401学习笔记(201-250题)

201、An engineer attempts to configure a trunk between switch SW1 and switch SW2 using DTP, but the trunk does not form. Which command should the engineer apply to switch SW2 to resolve this issue? A. switchport mode dynamic desirable B. switchport mode a…

GNU make 中文手册 第一二章 概述与介绍

一、第一章&#xff1a;概述 准备知识 在开始我们关于 make 的讨论之前&#xff0c;首先需要明确一些基本概念&#xff1a; 编译&#xff1a;把高级语言书写的代码&#xff0c;转换为机器可识别的机器指令。编译高级语言后生成的指令虽然可被机器识别&#xff0c;但是还不能…

小程序 npm sill idealTree buildDeps 安装一直没反应

目录 一、问题 二、解决 1、删除.npmsrc 、清除缓存 2、更换镜像源 3、最终检测 一、问题 记录&#xff1a;今天npm 一直安装不成功 显示&#xff1a;sill idealTree buildDeps 我的版本&#xff1a; 我百度到换镜像源安装方法&#xff0c;但我尝试后&#xff0c;依然…

CUDA性能指南

CUDA性能指南 文章目录CUDA性能指南5.1 整体性能优化策略5.2 最大化利用率5.2.1 应用程序层次5.2.2 设备层次5.2.3 多处理器层次5.2.3.1 占用率计算5.3 最大化存储吞吐量5.3.1 设备与主机之间的数据传输5.3.2 设备内存访问5.4最大化指令吞吐量5.4.1 算数指令5.4.2 控制流指令5.…

前端 ES6 环境下 require 动态引入图片以及问题

前端 ES6 环境下 require 动态引入图片以及问题require 引入图片方式打包体积对比总结ES6 环境中&#xff0c;通过 require 的方式引入图片很方便&#xff0c;一直以来也没有出过什么问题&#xff0c;后来项目中&#xff0c;需要动态引入图片。 require 动态引入也容易实现&am…

第一章 Kafka快速实战与基本原理

第一章 Kafka快速实战与基本原理 1、介绍 Kafka 是最初由 Linkedin 公司开发的&#xff0c;是一个分布式、支持分区的&#xff08;partition&#xff09;、多副本的&#xff08;replica&#xff09;&#xff0c;基于 zookeeper 协调的分布式消息系统&#xff0c;它最大的特性就…

分布式(四)

五、分布式锁 1. 概念 1.1 本地锁 使用ReetrantLock类和synchronized关键字JDK自带的本地锁来控制一个JVM进程内的多个线程对本地共享资源的访问。 1.2 分布式锁 分布式系统下&#xff0c;不同的服务器/客户端通常运行在独立的JVM进程上。 多个JVM进程共享一份资源的话&…