likes
comments
collection
share

React源码系列(五):Fiber架构&双缓存

作者站长头像
站长
· 阅读数 26

前言

这是React源码系列专栏的第五篇文章,预计写10篇左右,之前的文章请查看文末,通过本专栏的学习,相信大家可以快速掌握React源码的相关概念以及核心思想,向成为大佬的道路上更近一步; 本章我们学习Fiber架构和双缓存,本课程源码基于v18.2.0版本;

同步递归出现的问题

我们来复习一下,在之前章节 React设计理念&架构 讲过老的React架构用的是同步递归,同步递归调用会出现一个问题,也就是一旦开始渲染,就不能停止了,直到渲染出完整的树结构。也就是说会造成主线程被持续占⽤,造成的后果就是主线程上的布局、动画等周期性任务就⽆法立即得到处理,造成视觉上的卡顿,影响⽤户体验。

Fiber如何解决同步递归带来的问题

使用增量渲染(把渲染任务拆分成块,匀到多帧),将把工作分解成小单元,在完成每个单元之后,如果还有其他任务需要完成,我们将让浏览器中断渲染,也就是经常听到的 Fiber 架构,实现增量渲染的目的,是为了实现任务的可中断、可恢复,并给不同的任务赋予不同的优先级,最终达成更加顺滑的用户体验。 关键点:

  • 增量渲染(把任务拆解成小块,分布到各个帧中)
  • 更新时能够暂停,终止,复⽤渲染任务
  • 给不同类型的更新赋予优先级

再来看一下这个经典的图: 从下面第一幅图可以看出在这种情况下,函数堆栈的调用就像下图一样,层级很深,很长时间不会返回; 下面第二幅图 Fiber 分片模式下,浏览器主线程能够定期被释放,保证了渲染的帧率,函数的堆栈调用如下(波谷表示执行分片任务,波峰表示执行其他高优先级任务); React源码系列(五):Fiber架构&双缓存

Fiber架构核心思想

1、React16之前架构

在 React 16 之前,React 的渲染和更新阶段依赖的是如下图所示的两层架构: React源码系列(五):Fiber架构&双缓存

Reconciler 这一层负责对比出新老虚拟 DOM 之间的变化,Renderer 这一层负责将变化的部分应用到视图上,从 Reconciler 到 Renderer 这个过程是严格同步的。

2、React16之后架构

而在 React 16 之后,为了实现“可中断”和“优先级”,两层架构变成了如下图所示的三层架构: React源码系列(五):Fiber架构&双缓存

多出来的这层架构,叫作“Scheduler(调度器)”,调度器的作用是调度更新的优先级;

在这套架构模式下,更新的处理工作流变成了这样:首先,每个更新任务都会被赋予一个优先级。当更新任务抵达调度器时,高优先级的更新任务(记为 A)会更快地被调度进 Reconciler 层;此时若有新的更新任务(记为 B)抵达调度器,调度器会检查它的优先级,若发现 B 的优先级高于当前任务 A,那么当前处于 Reconciler 层的 A 任务就会被中断,调度器会将 B 任务推入 Reconciler 层。当 B 任务完成渲染后,新一轮的调度开始,之前被中断的 A 任务将会被重新推入 Reconciler 层,继续它的渲染之旅,这便是所谓“可恢复”。 以上,便是架构层面对“可中断”“可恢复”与“优先级”三个核心概念的处理。

可以回过头再来看一下之前章节 React设计理念&架构

Fiber架构能完成的工作

  • 任务分解 :Fiber最重要的功能就是作为工作单元,保存原生节点或者组件节点对应信息(包括优先级),这些节点通过指针的形似形成Fiber树;
  • 增量渲染:通过jsx对象和current Fiber的对比,生成最小的差异补丁,应用到真实节点上;
  • 优先级调度:Fiber节点上保存了优先级,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力,也为上层实现批量更新、Suspense提供了基础;
  • 保存状态:因为Fiber能保存状态和更新的信息,所以就能实现函数组件的状态更新,也就是hooks;

Fiber的含义

Fiber包含三层含义:

  • 作为架构来说,之前React15的Reconciler采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler。React16的Reconciler基于Fiber节点实现,被称为Fiber Reconciler;
  • 作为静态的数据结构来说,每个Fiber节点对应一个React element,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息;
  • 作为动态的工作单元来说,每个Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...);

Fiber作为架构

React Fiber 机制的实现(作为架构),是依赖下面这种数据结构(链表),每一个节点都是一个fiber。一个 fiber 包括了 child(第一个子节点)、sibling(兄弟节点)、return(父节点)属性。

function App() {
  return (
    <div>
      <h1>
        <p></p>
        <a></a>
      </h1>
      <h2></h2>
    </div>
  );
}

React源码系列(五):Fiber架构&双缓存

Fiber的数据结构

主要有DOM、Fiber树、状态数据、副作用四种标识;

// packages/react-reconciler/src/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;  // 组件类型 div、span、组件构造函数
  this.stateNode = null; // 真实dom节点, 如类组件的实例、原生 dom 实例, 而 function 组件没有实例, 因此该属性是空

  /** --------------------------作为架构 ----------------------------------**/

	// 构建 Fiber 树相关

  this.return = null;  //指向父节点
  this.child = null;   // 指向自己的第一个子级 Fiber 对象
  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;

  // Effects副作用相关
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  // 优先级相关的属性
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
	
  this.alternate = null; // alternate 指向当前 Fiber 在 workInProgress 树中的对应 Fiber
}React Fiber机制架构

Fiber双缓存

定义

在 React 中,DOM 的更新采用了双缓存技术,双缓存技术致力于更快速的 DOM 更新。 什么是双缓存?举个例子,使用 canvas 绘制动画时,在绘制每一帧前都会清除上一帧的画面,清除上一帧需要花费时间,如果当前帧画面计算量又比较大,又需要花费比较长的时间,这就导致上一帧清除到下一帧显示中间会有较长的间隙,就会出现白屏。 为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,这样的话在帧画面替换的过程中就会节约非常多的时间,就不会出现白屏问题。这种在内存中构建并直接替换的技术叫做双缓存。 React 使用双缓存技术完成 Fiber 树的构建与替换,实现DOM对象的快速更新。

Fiber双缓存创建

在 React 中最多会同时存在两棵 Fiber 树,当前在屏幕中显示的内容对应的 Fiber 树叫做 current Fiber 树,当发生更新时,React 会在内存中重新构建一颗新的 Fiber 树,这颗正在构建的 Fiber 树叫做 workInProgress Fiber 树。

在双缓存技术中,workInProgress Fiber 树就是即将要显示在页面中的 Fiber 树,当这颗 Fiber 树构建完成后,React 会使用它直接替换 current Fiber 树达到快速更新 DOM 的目的,因为 workInProgress Fiber 树是在内存中构建的所以构建它的速度是非常快的。

一旦 workInProgress Fiber 树在屏幕上呈现,它就会变成 current Fiber 树。

在 current Fiber 节点对象中有一个 alternate 属性指向对应的 workInProgress Fiber 节点对象,在 workInProgress Fiber 节点中有一个 alternate 属性也指向对应的 current Fiber 节点对象。

Mount阶段

1.创建了fiberRoot和rootFiber两个节点: React源码系列(五):Fiber架构&双缓存

2.根据jsx创建workInProgress Fiber树: React源码系列(五):Fiber架构&双缓存 3.将workInProgress Fiber切换成current Fiber: React源码系列(五):Fiber架构&双缓存

Update阶段

1.根据current Fiber创建workInProgress Fiber; React源码系列(五):Fiber架构&双缓存 2.将workInProgress Fiber切换成current Fiber; React源码系列(五):Fiber架构&双缓存

小结

本章我们学习了Fiber架构和双缓存,接下来的文章将进入React render阶段源码分析,欢迎继续跟随本专栏一起学习;

参考链接

React源码系列