聊聊React(3) - Fiber架构与Fiber双缓存

232 阅读5分钟

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;
  
  ......
}

静态类型相关属性

image.png

存储一些基本的节点信息,这里着重说一下 this.tagthis.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组件

image.png

Fiber结构特有

image.png

主要用于实现fiber tree,也就是通过链表的形式链接上一级节点,下一级第一个子节点,兄弟节点等

Fiber 解决了哪些问题

  1. 工作单元 任务分解 :Fiber作为工作单元,保存原生节点或者组件节点对应信息(包括优先级),这些节点通过指针的形似形成Fiber树。
  2. 增量渲染:通过jsx对象和current Fiber的对比,生成最小的差异补丁,应用到真实节点上。
  3. 根据优先级暂停、继续、排列优先级:Fiber节点上保存了优先级,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力,也为上层实现批量更新、Suspense提供了基础。
  4. 保存状态:因为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>
    </>
  );
}

所有节点通过深度遍历所要经历的过程

image.png

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

  1. 在mount阶段会先形成fiberRoot 与 rootFiber 节点,用于形成 cuurent Fiber tree。

  2. 在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 FiberworkInProgress Fiber两颗Fiber树,Fiber双缓存指的就是,在经过reconcile(diff) 形成了新的workInProgress Fiber然后将workInProgress Fiber切换成current Fiber应用到真实dom中,存在双Fiber的好处是在内存中形成视图的描述,在最后应用到dom中,减少了对dom的操作。

current Fiber 与 workInProgress Fiber的切换过程

举例🌰如上👆

mount时

  1. 生成 fiberRoot 与 rootFiber节点

image.png

  1. 根据生成的jsx 生成 workInProgress Fiber

image.png

  1. 切换current指针把workInProgress Fiber切换成current Fiber

image.png

update时

  1. 根据最新的jsx 生成 workInProgress Fiber

image.png

  1. 切换 current指针

image.png