Fiber架构
起源与实现
为什么有fiber?它做了什么?
因为 Stack 模式 慢,react16之前对比VirtualDom的过程采用的是递归的形式,这种形式存在一个问题就是,如果遇到组件量比较大,VirtualDom层级比较深,那么在diff的过程中就会非常的耗时,以至于js占用主线程无法让出执行权,导致其他交互无法实现,最简单的例子就是用户在这个过程中点击了一个按钮,但是页面是卡主的无法进行渲染,导致用户体验较差。
Fiber结构的设计可以理解成 小批量, 多批次 , 可中断的执行过程,不再用递归的形式,而是采用循环的方式,将大的渲染任务拆分成一个个小任务, 利用浏览器的空闲时间去执行小任务,在执行一个任务单元后,查看是否有其他高优先级的任务,如果有,放弃占用线程,让出执行权去先执行优先级高的任务。
Fiber结构
// ReactFiber.old.js
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// 静态类型,节点的基本信息
this.tag = tag; // 对应react组件的类型
this.key = key; // key值
this.elementType = null; // html元素类型
this.type = null; // 存储节点的字符串 'html标签名'/'function'
this.stateNode = null; // 指向真实dom节点 / FiberRootNode
// Fiber结构特有, 形成fiber树
this.return = null; // 上一层节点
this.child = null; // 第一个子节点
this.sibling = null; // 兄弟节点
this.index = 0;
this.ref = null; // ref属性
// 工作单元相关,计算state
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
// 当前应用启动模式
this.mode = mode;
// EffectList相关
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
// 优先级
this.lanes = NoLanes;
this.childLanes = NoLanes; // 子节点的优先级
// 链接currentFiber 和 workInProgressFiber 的指针
this.alternate = null;
......
}
静态类型相关属性
存储一些基本的节点信息,这里着重说一下 this.tag 和 this.type
this.tag的结构
export type WorkTag =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 16
| 17
| 18
| 19
| 20
| 21
| 22
| 23
| 24
| 25;
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;
export const TracingMarkerComponent = 25;
可以看出react中对不同了组件类型使用了不同的number作为区分
this.type的作用
存储节点的字符串 'html标签名'/'function',说白了存储的就是一个名称,如果是一个div标签那么就是'div',如果是class or function 类型的组件那么就是 function,至于如何区分 是class组件还是function组件,在Component方法的原型链上有一个 isReactComponent 属性,用于区分是 class组件 还是 function组件
Fiber结构特有
主要用于实现fiber tree,也就是通过链表的形式链接上一级节点,下一级第一个子节点,兄弟节点等
Fiber 解决了哪些问题
- 工作单元 任务分解 :Fiber作为工作单元,保存原生节点或者组件节点对应信息(包括优先级),这些节点通过指针的形似形成Fiber树。
- 增量渲染:通过jsx对象和current Fiber的对比,生成最小的差异补丁,应用到真实节点上。
- 根据优先级暂停、继续、排列优先级:Fiber节点上保存了优先级,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力,也为上层实现批量更新、Suspense提供了基础。
- 保存状态:因为Fiber能保存状态和更新的信息,所以就能实现函数组件的状态更新,也就是hooks。
Fiber双缓存
在react渲染更新的过程中,内存中存在两个树,一颗是真实dom对应在内存中的Fiber节点会形成Fiber树,也叫current Fiber tree; 另一颗是对比当前更新的vitual-dom 形成的 fiber tree 也叫做 workInprogressFiber tree,这两颗树上的节点 通过节点的 alternate进行链接。
举个例子
import React, { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return (
<>
<h1
onClick={() => {
setCount(() => count + 1);
}}
>
<p title={count}>{count}</p> haoren
</h1>
</>
);
}
所有节点通过深度遍历所要经历的过程
构建workInProgress Fiber发生在createWorkInProgress中,它能创建或者复用Fiber节点
-
在mount阶段会先形成fiberRoot 与 rootFiber 节点,用于形成 cuurent Fiber tree。
-
在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的操作。
current Fiber 与 workInProgress Fiber的切换过程
举例🌰如上👆
mount时
- 生成 fiberRoot 与 rootFiber节点
- 根据生成的jsx 生成 workInProgress Fiber
- 切换current指针把workInProgress Fiber切换成current Fiber
update时
- 根据最新的jsx 生成 workInProgress Fiber
- 切换 current指针