「React 深入」知悉Fiber,方能百战不殆~

2023-11-23 18:59

本文主要是介绍「React 深入」知悉Fiber,方能百战不殆~,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

React v16以上的版本引入了一个非常重要的概念,那就是fiber,实际上fiberreact团队花费两年的时间重构的架构,在之前的文章中也提及到了fiber,那么fiber架构究竟是什么,为什么要使用fiber

在正式开始前,我们可以先看看以下几个问题:

  • 什么是fiberfiber解决了什么问题?
  • fiber中,保存了哪些信息,这些信息的作用是什么?
  • 如何将jsx转化为fiber链表,又是如何将链表链接起来的
  • fiber是如何更新的?
  • fiberstack相比,主要解决了哪些方面的问题?

先附上今天的学习图谱,方便我们更好的理解:

走进 Fiber

什么是Fiber

在一个庞大的项目中,如果有某个节点发生变化,就会给diff带来巨大的压力,此时想要要找到真正变化的部分就会耗费大量的时间,也就是说此时,js会占据主线程去做对比,导致无法正常的页面渲染,此时就会发生页面卡顿、页面响应变差、动画、手势等应用效果差

为了解决这一问题,react团队花费两年时间,重写了react的核心算法reconciliation,在v16中发布,为了区分reconciler(调和器),将之前的reconciler称为stack reconciler,之后称作fiber reconciler(简称:fiber)

简而言之,fiber就是v16之后的虚拟DOMReact在遍历的节点的时候,并不是真正的DOM,而是采用虚拟的DOM

v16之前,React是如何遍历节点的?

我们先看看下面这张图:

遍历的顺序为:A => B => D => E => C => F => G

v16之前,react采用的是深度优先遍历去遍历节点,转化为代码为:

 const root = {key: 'A',children: [{key: 'B',children: [{key: 'D',},{key: 'E',},],},{key: 'C',children: [{key: 'F',},{key: 'G',},],},],};const walk = dom => dom.children.forEach(child => walk(dom));walk(root); 

可以看出这种遍历采取的递归遍历,如果这颗树非常的庞大,那么对应的栈也会越来越深,如果其中发生中断,那么整颗树都不能恢复。

也就是说,在传统的方法中,在寻找节点的过程中,花费了1s,那么这1s就是浏览器无法响应的,同时树越庞大,卡顿的效果也就越明显

所以在v16之前的版本,无法解决中断树庞大的问题

知悉fiber

在上面的介绍中,我们知道fiber实际上是一种核心算法,为了解决中断树庞大的问题,那么接下来我们先来了解下fiber

虚拟DOM是如何转化成fiber的

先看看最常见的一段jsx代码:

const Index = (props)=> {return (<div>大家好,我是小杜杜</div>);
} 

这段代码就是最普通的jsx,经过babel会编译成React.createElement的形式

再来看看绑定的结构:

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

之后会走一个beginWork的方法,这个方法会通过tag去判断这段代码的element对象,再之后会调用reconcileChildFibers函数,这个函数就是转化后的fiber结构

element、fiber和DOM元素 的关系

1.element对象就是我们的jsx代码,上面保存了propskeychildren等信息
2.DOM元素就是最终呈现给用户展示的效果
3.而fiber就是充当elementDOM元素的桥梁,简单的说,只要elemnet发生改变,就会通过fiber做一次调和,使对应的DOM元素发生改变

其中有一个tag,这个tag的类型就是判断 element对应那种的fiber,如:

beginWork 的入参

在这里我将这三个入参单独说一下,因为这三个参数比较重要,我们要有一个基础的概念

  • current:在视图层渲染的树
  • workInProgress:这个参数尤为重要,它就是在整个内存中所构建的 Fiber树,所有的更新都发生在workInProgress中,所以这个树是最新状态的,之后它将替换给current
  • renderLanes:跟优先级有关,优先级也是一块非常大的点,这里先不介绍
element 和 fiber 的对应表

在这里总结了一些比较常用的对照表,供大家参考:

fiberelement
FunctionComponent = 0函数组件
ClassComponent = 1类组件
IndeterminateComponent = 2初始化的时候不知道是函数组件还是类组件
HostRoot = 3根元素,通过reactDom.render()产生的根元素
HostPortal = 4ReactDOM.createPortal 产生的 Portal
HostComponent = 5dom 元素(如<div>
HostText = 6文本节点
Fragment = 7<React.Fragment>
Mode = 8<React.StrictMode>
ContextConsumer = 9<Context.Consumer>
ContextProvider = 10<Context.Provider>
ForwardRef = 11React.ForwardRef
Profiler = 12<Profiler>
SuspenseComponent = 13<Suspense>
MemoComponent = 14React.memo 返回的组件
SimpleMemoComponent = 15React.memo 没有制定比较的方法,所返回的组件
LazyComponent = 16<lazy />

fiber 保存了什么?

接下来我们看看fiber中保存了什么,如:

源码部分在packages/react-reconciler/src/ReactFiber.old.js中的FiberNode

在这里我们直接来看看对应的type(位置在同目录下的ReactInternalTypes.js)

然后简单的分为四个部分,分别是InstanceFiberEffectPriority

Instance

Instance:这个部分是用来存储一些对应element元素的属性

export type Fiber = {tag: WorkTag,// 组件的类型,判断函数式组件、类组件等(上述的tag)key: null | string, // keyelementType: any, // 元素的类型type: any, // 与fiber关联的功能或类,如<div>,指向对应的类或函数stateNode: any, // 真实的DOM节点...
} 
Fiber

Fiber:这部分内容存储的是关于fiber链表相关的内容和相关的propsstate

export type Fiber = {...return: Fiber | null, // 指向父节点的fiberchild: Fiber | null, // 指向第一个子节点的fibersibling: Fiber | null, // 指向下一个兄弟节点的fiberindex: number, // 索引,是父节点fiber下的子节点fiber中的下表ref:| null| (((handle: mixed) => void) & {_stringRef: ?string, ...})| RefObject,// ref的指向,可能为null、函数或对象pendingProps: any,// 本次渲染所需的propsmemoizedProps: any,// 上次渲染所需的propsupdateQueue: mixed,// 类组件的更新队列(setState),用于状态更新、DOM更新memoizedState: any, // 类组件保存上次渲染后的state,函数组件保存的hooks信息dependencies: Dependencies | null,// contexts、events(事件源) 等依赖mode: TypeOfMode, // 类型为number,用于描述fiber的模式 ...
} 
Effect

Effect:副作用相关的内容

export type Fiber = {... flags: Flags, // 用于记录fiber的状态(删除、新增、替换等) subtreeFlags: Flags, // 当前子节点的副作用状态 deletions: Array<Fiber> | null, // 删除的子节点的fiber nextEffect: Fiber | null, // 指向下一个副作用的fiber firstEffect: Fiber | null, // 指向第一个副作用的fiber lastEffect: Fiber | null, // 指向最后一个副作用的fiber...
} 
Priority

Priority: 优先级相关的内容

export type Fiber = {...lanes: Lanes, // 优先级,用于调度childLanes: Lanes,alternate: Fiber | null,actualDuration?: number,actualStartTime?: number,selfBaseDuration?: number,treeBaseDuration?: number,...
} 

链表之间如何连接的?

Fiber中我们看到有returnchildsibling这三个参数,分别指向父级、子级、兄弟,也就是说每个element通过这三个属性进行连接

举个栗子🌰:

const Index:React.FC<any> = (props)=> {return (<div>大家好,我是小杜杜<div>走进fiber的世界</div><p>收藏 === 学会</p></div>);
} 

那么按照之前讲的就会转化为:

Fiber 执行阶段

初始化(mount)阶段

在上文已经说过,react首次执行(初始化阶段)会以ReactDOM.render为入口,然后开始执行,由于调用的函数实在过多,这里我就简化一些,方便我们更好理解

createFiber

createFiber:这个函数会创建rootFiber,也就是react应用的根,会调用FiberNode函数来进行对应的构建工作

位置:packages/react-reconciler/src/ReactFiberRoot.old.js

const createFiber = function( tag: WorkTag,pendingProps: mixed,key: null | string,mode: TypeOfMode, ): Fiber {// $FlowFixMe: the shapes are exact here but Flow doesn't like constructorsreturn new FiberNode(tag, pendingProps, key, mode);
}; 

FiberNode就是上述讲的构造函数

createFiberRoot

createFiberRoot:它会调用FiberRootNode构造函数,创建fiberRoot,并且指向真正的根节点(root

位置:packages/react-reconciler/src/ReactFiberRoot.old.js

export function createFiberRoot(containerInfo: any,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);if (enableSuspenseCallback) {root.hydrationCallbacks = hydrationCallbacks;}const uninitializedFiber = createHostRootFiber(tag);root.current = uninitializedFiber; // 指向rootFiberuninitializedFiber.stateNode = root; // 指向fiberRootinitializeUpdateQueue(uninitializedFiber);return root;
} 

顺便看下FiberRootNode函数

beginWork

beginWork:这个函数正真走我们的jsx代码,也就是上面讲解的链表之间如何连接的部分

那么我们再把上面的图,拿出来,看看遍历的流程:

首先,我们要知道reactfiber结构的创建和更新都是深度优先遍历

1.首先会判断当前组件是类组件还是函数式组件,类组件tag为1,函数式为0
2.然后发现div标签,标记tag 为 5
3.发现div下包含三个部分,分别是,文本:大家好,我是小杜杜div标签p标签
4.首先遍历文本:大家好,我是小杜杜,下面无节点,标记tag 为 6
5.在遍历div标签,标记tag 为 5,此时下面有节点,所以对节点进行遍历,也就是文本走进fiber的世界,标记tag 为 6
6.同理最后遍历p标签

整个的流程就是这样,通过tag标记属于哪种类型,然后通过returnchildsibling这三个参数来判断节点的位置

更新(Update)阶段

接下来看看更新阶段,举个例子:

const Index:React.FC<any> = (props)=> {const [count, setCount] = useState(0)return (<div><div>数字:{count}</div><Button onClick={() => setCount(v => v + 1)} >点击</Button></div>);
} 

当我们点击按钮后,会走createWorkInProgress方法,这个方法会将创建一个新的workInProgress fiber,然后还是会深度优先遍历,对发生改变的fiber打上不同的flags副作用标签,然后通过副作用(Effect)中的nextEffectfirstEffectlastEffect等字短行程一个Effect List的链表

再来看对应的源码(位置在packages/react-reconciler/src/ReactFiber.old.js):

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {let workInProgress = current.alternate; //以alternate作为基础if (workInProgress === null) { //这里判断是初始化还是更新阶段workInProgress = createFiber(current.tag,pendingProps,current.key,current.mode,);workInProgress.elementType = current.elementType;workInProgress.type = current.type;workInProgress.stateNode = current.stateNode;...// 这部分是重置所有的副作用workInProgress.flags = current.flags & StaticMask;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;
} 

总的来说在更新阶段,更新阶段会将currentalternate作为基础,然后复制一部分,进行节点的更新,返回一个新的workInProgress

这里需要注意一点,current fiberworkInProgress fiber中的alternate是相互指向的,当新的 workInProgress fiber 创建完成后,fiberRootcurrent字段会从current fiber中的rootFiber变为workInProgress fiber中的rootFiber

fiber 带来后的变化

最后,我们再来看看更改前后的fiber图,发生了怎样的变化:

Stack Example

Fiber Example

可以看出来,fiber明显比stack要流畅很多,代表响应速度变快,宽度的变化也不会引发卡顿

对比Vue

我们经过上面的了解,已经知道React Fiber实际上是无差别刷新,他是将整个变化的树作为更改,而Vue是精确的将变化的节点进行替换,那是不是说Vue要强于React

其实这个话题非常有争议,就我个人而言,Vue的精确的替换也是具有代价的,至于两者孰强孰弱,作为一个小白也不好去多做评论,我们主要是学习思想,毕竟思想才是最重要的

React自身也会提供一些优化的方法,如useMemouseCallback等,我们一定要善用于这些API,帮助我们更好的去玩React

结语

React Fiber可以说是React的基石,很多方面都离不开它,学习fiber是不可缺少的一部分

实际上,这篇文章笔者已经推翻过两三次,主要原因是fiber实际上非常大,里面涉及的概念也十分琐碎,加上关于fiber的文章也非常多,我不知道该如何更好的去呈现出来

其次,这篇文章算是个入门级的fiberfiber比较难的概念都没有涉及到,阅读起来相对轻松一点,比较难的是优先级、调度、调和等模块

相比于更高级的模块,应该把架子搭起来,由浅入深,一点一点的慢慢啃,(如有不对的地方请在评论区留言指出~)

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

这篇关于「React 深入」知悉Fiber,方能百战不殆~的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/420062

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo