前言
了解React的多少都见过这句话:React16之后,改用了Fiber 架构。那么,到底什么是Fiber架构?之前的架构是什么?为什么要使用Fiber架构代替之前的呢?
其实关于Fiber架构的解读可深可浅,然而我曾经对Fiber架构的认知是👇
emmm这个确实过于浅了。
此篇文章作为我的学习记录,将概括性地总结讲述Fiber架构的关键概念。具体的深入内容将会在之后的文章中介绍。
- 本文前置知识:Virtual DOM
正文
栈式架构(Stack Reconcilation)
在React Fiber架构之前,React使用的是栈式架构(Stack Reconcilation) ,它基于递归的方式来进行 Virtual DOM 的比较和更新。
尽管 Stack Reconciler 在初期推动了 React 的发展,但随着 Web 应用程序的复杂性增加和用户需求的提升,它的同步执行特性在处理大型应用或复杂交互时表现出了一些局限性。
js
{
type : "div",
props : {
id : "list",
children : [
{
type : "ul",
props : {
children : [{
type : "li",
props : {
children : "apple"
}
},//...
}]
}
}
]
}
}
Stack架构在进行两棵虚拟DOM树对比的时候,递归遍历上面的结构。这种同步执行的特性意味着一旦开始更新操作,需要一直执行完所有比较和更新,无法中断或分段处理。
虽然虚拟 DOM 是 JS 层面的计算,比起真实的DOM操作已经有了很大的优化,但是在应用程序中有大量的组件和复杂的数据结构时,递归的比较和更新依然会消耗大量的计算资源和时间,导致页面在更新过程中出现卡顿现象,直接影响到用户的交互体验。
另一方面, Stack Reconciler 没有引入任务优先级的概念,所有更新任务都按照生成的顺序依次同步执行。这意味着如果一个高优先级的更新任务需要立即响应,但此时正在进行的低优先级更新任务还未完成,就会造成用户体验的延迟和不流畅。
(举个例子:用户在输入时,不到1s的延迟就会觉得很卡;在loading时,几秒等待也能接受。所以前者高优先级,后者低优先级)
简单总结一下,Stack Reconciler性能限制主要分为两类:
-
CPU瓶颈:即应用的计算需求超过了CPU的处理能力。这里的CPU 瓶颈通常指的是由于大量的 Virtual DOM 操作、组件更新或复杂的计算任务导致的 CPU 资源消耗过高,主线程(负责UI渲染的线程)被长时间占用,从而影响应用的响应速度和用户体验。
-
I/O瓶颈 :I/O瓶颈主要与网络延迟有关,是客观存在的。前端只能尽量减少其对用户的影响,例如区分不同操作的优先级。
Fiber架构的关键概念
1. Fiber节点
Fiber 节点是 Fiber 架构的核心概念之一,它是一种虚拟DOM的实现方式。
Fiber本质上是一个对象, 使用了链表结构。和之前的递归树实现 Virtual DOM不同的是,对象之间使用链表的结构串联。一个 fiber包括了 child(第一个子节点)、sibling(同级节点)、return(上一级节点)等属性。
如上图,fiber的child 指向下一级元素,sibling 指向同级元素,return 指向上一级元素。
这种结构和递归树相比,最重要的优势是在进行虚拟树的对比计算时,过程可以中断和恢复。
源码中的Fiber 对象(已简化处理):
js
function FiberNode(
tag: WorkTag, // 标记节点类型,例如 FunctionComponent、ClassComponent、HostRoot 等
pendingProps: mixed, // 待处理的props
key: null | string, // 节点的key
mode: TypeOfMode, // 渲染模式,例如是否在严格模式下或是否是异步更新
) {
// 实例属性
this.tag = tag;
this.key = key;
this.elementType = null; // 组件的元素类型,可能为定义改组件的函数或类、DOM元素类型等
this.type = null; // 实际的 JavaScript 对象类型(如函数、类、原生 DOM 节点等)
this.stateNode = null; // 节点对应的实例或 DOM 节点
// Fiber树结构
this.return = null; // 上一级节点
this.child = null; // 第一个子节点
this.sibling = null; // 下一个同级节点
this.index = 0; // 在上一级节点中的索引
this.ref = null;
// Props和State
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
if (enableProfilerTimer) {
// 以下是性能相关的属性,在开发模式下使用
// ...
}
// 开发模式下的调试相关属性
if (__DEV__) {
//...
}
}
2. 调度器Scheduler
React16之后,新增了调度器Scheduler组件,负责管理和调度任务执行。
前面提到,用户对于不同操作的感知不同,如果在网络延迟客观存在的情况下不对各种操作的优先级区分,细微的延迟就会造成用户体验的不流畅。
而Scheduler就是解决这个问题的。React定义了不同的优先级级别,如 Immediate
(最高优先级,用于处理用户交互)、Normal
(默认优先级,一般的更新任务)、Low
(低优先级,如后台任务)等。调度器可以根据任务的优先级来安排它们的执行顺序,以尽快地响应用户的操作,提升用户体验。
3. 时间切片 TimeSlice
Fiber架构引入时间切片(Time Slicing) 的概念,即将大的渲染任务分解为多个较小的片段,每个片段都可以在一帧内完成,这样可以防止长时间的任务阻塞主线程,保持界面的流畅性。
时间切片允许React在每个片段之间执行其他优先级更高的任务,从而在不同任务之间找到一个平衡点,提高整体的响应性和用户体验。
4. 双重缓冲Double Buffering
双重缓冲是一种渲染优化技术,其使用两个Fiber树 来管理渲染,即当前树 current tree 和工作树 work-in-progress tree。当前树代表屏幕上当前显示的内容,而工作树用于准备下一次的渲染更新,用以实现平滑的更新。