Fiber
Fiber 的前因后果
Stack Reconciler
在 React 15.x 版本及以前,Reconciliation 算法使用栈调和器( Stack Reconciler )实现,但这个时期的栈调和器存在一些缺陷。
对于之前 JSX 方法执行后返回 ReactElement,因为它只记录自身的数据,如组件的类型、属性和子元素,但是不会记录与它相关的父节点,兄弟节点的关系。
导致 react 在调和的时候不能暂停渲染任务,也不能切分任务。
此外,它无法有效平衡组件更新渲染和动画相关任务的执行顺序,这也就意味着无法划分任务的优先级,可能导致重要任务的卡顿,以及动画掉帧等问题。因此,Stack Reconciler 的实现方式需要改进。
基于 Stack Reconciler 的页面效果

Fiber Reconciler
在 React 16 版本中,引入了 Fiber 调和器(Fiber Reconciler)来替代栈调和器,从而推出了新的 Reconciliation 算法。Fiber Reconciler 利用调度器(Scheduler)来帮助处理组件的渲染/更新工作。此外,引入 Fiber 这个后,原来的 React Element Tree 有了一棵对应的 Fiber Node Tree。在 diff 两棵 React Element Tree 的差异时,Fiber Reconciler 基于 Fiber Node Tree 使用 diff 算法,通过 Fiber node 的 return、child、sibling 属性能够更方便地遍历 Fiber Node Tree,从而更高效地完成 diff 算法。
Fiber 的出现,可以最明显的两层含义:
- 作为 element 元素,它保存该组件的类型和 dom 信息
- 保存本次更新中该组件改变的状态、要执行的任务(需要插入/删除/更新等)
Fiber 调度的优点:
- 能够将可中断的任务切片处理;
- 能够调整任务优先级、重置并复用任务;
- 可以在父子组件任务间前进后退切换任务;
- render 方法可以返回多个元素(即可以返回数组);
- 支持异常边界处理异常;
基于 Fiber Reconciler 的页面效果

通过上面两组动画能够比较出来,Fiber 的动画比 Stack 的动画要顺滑很多。
element,Fiber、Dom 三者的关系
- element 是开发者写的 jsx 语法,写的是元素结构,保存了 props,children 等内容
- dom 是元素在浏览器上用户直观可以看到的内容
- fiber 是 element 和 dom 之间的连接桥,每一个 element 对应一个 fiber 对象
在 React 中,我们通过 JSX 写入以下的代码
<div className="sanmu" sex="male">
  <p>三木</p>
</div>
经过 babel 转义,会变成以下的方法,将一个 html 的结构编程一个方法函数,其中 div 的属性,都变成_jsx 方法的一个 key-value
import { jsx as _jsx } from 'react/jsx-runtime';
/*#__PURE__*/ _jsx('div', {
  className: 'sanmu',
  sex: 'male',
  children: /*#__PURE__*/ _jsx('p', {
    children: '\u4E09\u6728',
  }),
});
通过上面的方法在转成 fiber 对象,多个 fiber 对象构成了一课 fiber 树,fiber 树是构成 DOM 树的数据模型,fiber 树的任何改动,最终都体现在 DOM 树中。
换句话说:
开发人员通过编写JSX代码来形成Fiber,之后Fiber来构建DOM。

element 与 fiber 之间的对应关系
通过这个对应关系,当遇到不同的类型时,可以创建不同结构的 fiber 对象。
展示一些常见的类型:
| element | |
|---|---|
| FunctionComponent = 0 | 函数组件 | 
| ClassComponent = 1 | 类组件 | 
| IndeterminateComponent = 2 | 初始化不知道是函数组件还是类组件 | 
| HostRoot = 3 | |
| HostPortal = 4 | ReactDOM.createPortal 产生的 Portal | 
| HostComponent = 5 | dom 元素,如 div,span | 
| HostText = 6 | 文本节点 | 
| Fragment = 7 | React.Fragment | 
| Mode = 8 | React.StrictMode | 
| ContextConsumer = 9 | Context.Consumer | 
| ContextProvider = 10 | Context.Provider | 
| ForwardRef = 11 | React.ForwardRef | 
| … | 
Fiber 是什么
fiber 既是一个数据结构,也是一个执行单元。
一、数据结构
1、Fiber 保存的信息
fiber 有很多属性,大体分为以下 5 种:
- 结构信息,用于表示节点在树中的位置。
- 节点类型信息,tag 表示节点的分类,type 保存具体类型值,例如 div,mycomp。
- 节点状态,节点的组件实例,props,state 等。
- 副作用 effectTag,通过 nextEffect 连接。
- 替身,React 会构建一颗新的树——workInProgress Tree,通过 alternate 指向旧树的相同节点。
export type Fiber = {
  /**
  *  作为静态数据结构的属性
  */
  tag: WorkTag,   // fiber类型,例如函数组件、类、Ref等等
  key: null | string, // 子节点的唯一标识,用于调和子节点使用
  elementType: any,  // 节点元素类型,是具体的类组件、函数组件等,elmentType 与 element 中的type一样
  type: any, fiber节点的类型,如span,div
  /** 指向真实的dom元素
   * 1. 若当前fiber节点是dom元素,则对应的是真实DOM元素;
   * 2. 若当前是function component,则值为null;
   * 3. 若当前是class component,则值为class初始化出来的实例;
   * 4. 若当前是 host component,即树的根节点,stateNode为 FiberRootNode;
   */
  stateNode: any,  // 节点实例(状态),若不存在走创建,存在走更新
  /**
  *  作为静态数据结构的属性
  */
  return: Fiber | null, // 指向父级fiber
  child: Fiber | null, // 指向子级fiber
  sibling: Fiber | null,// 指向兄弟fiber
  index: number, // 索引
  ref:
    | null
    | (((handle: mixed) => void) & {_stringRef: ?string, ...})
    | RefObject,
  /**
  *  作为动态的工作单元的属性
  */
  pendingProps: any, // 在一次更新中,代表element创建
  memoizedProps: any, // 记录上一次更新完毕后的props
  updateQueue: mixed, // 类组件存放setState更新队列,函数组件存放
  memoizedState: any // 类组件保存state信息,函数组件保存hooks信息,dom元素为null
  dependencies: Dependencies | null, // context或者时间的依赖项
  mode: TypeOfMode, // fiber树的模式,比如ConcurrentMode
  nextEffect: Fiber | null, // 串联下一个副作用的fiber节点
  firstEffect: Fiber | null, // 副作用的第一个节点
  lastEffect: Fiber | null, // 副作用的最后一个节点
  // 副作用
  flags: Flags,  // 节点更新的优先级
  subtreeFlags: Flags, // 子节点的更新情况
  deletions: Fiber[], // 子节点需要删除的节点
  // 调度优先级相关
  lanes: Lanes,
  childLanes: Lanes,
  alternate: Fiber | null, // 双缓存树,指向缓存fiber。用于更新阶段,两棵树相互交替
};
tag
fiber 中的 tag 类型是 workType,用于标记不同的 react 类型
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2;
export const HostRoot = 3;
export const HostPortal = 4;
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;
...
key 和 type
key 和 type 用于 react diff 过程中判断 fiber 是否可以复用。
key 为用户定义的唯一值。
type 定义与此 fiber 关联的功能或者类。对于组件,它指向函数或者类本身;对于 DOM 元素,它指向 HTML Tag。
stateNode
记录当前 fiber 对应的真实 dom 节点或者当前虚拟组件的实例,方便真实dom的追踪和 Ref。
2、Fiber 节点与 Fiber 节点之间的联系
每个 Fiber 记录了它上面的信息,而且 Fiber 与 Fiber 之间也会相互关联。

每个 Fiber 通过 child、sibling、return 三个属性建立连接。
- child指子 Fiber 节点
- sibling指兄弟 Fiber 节点
- return指父 Fiber 节点
二、执行单元
Fiber 可以被理解为一个执行单元。每次执行完一个执行单元,React 就会检查现在是否还有多少时间,如果没有时间就将控制权让出去。
- 首先 React 向浏览器请求调度。
- 若浏览器在一帧时间内有空闲时间,则去判断是否有可疑执行的任务。- 如果存在,则执行它。若执行完成后还有时间,继续执行下一个。
- 如果不存在,则将控制权交给浏览器。
 
几个概念
workInProgress
正在内存中构建的 fiber 树称为WorkInProgress Fiber树。在一次更新中,通过定义一个workInProgress变量来指向下一个工作单元。更新之后,workInProgress树上的状态是最新的状态,它将变成current树用于渲染视图。
current
正在视图渲染的树,也就是当前页面上 dom 所对应的那个节点树。
副作用
节点的增删改,比如修改了 state、props 等数据,除了数据变化之外,还会造成 dom的变化,这种在 render阶段不能完成的工作,称之为副作用。在Reconciliation过程中发现的“副作用”保存在fiber的effectTag中,在本次渲染的所有包含副节点的 fiber 通过它们的nextEffect连接起来。
Fiber 的执行阶段
从根节点开始渲染和调度的过程可以分为两个阶段:render阶段和commit阶段。
- render阶段:可以中断,会找出所有节点的变更。
- commit阶段:不可以中断,会执行所有的变更。
render 阶段
在这个阶段,我们要找出所有节点的变更,如节点新增、删除、属性变更等等。这些变更被统称为副作用(effect),vdom 变为 fiber 的过程称之为“reconcile”。在这个阶段中,我们会构建一棵 Fiber Tree 并通过连接这些变更产生一个 effect list。
在workLoop中,通过performUnitOfWork函数返回下一个工作单元。
文件地址:/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}
在performUnitOfWork中,workInProgress会一直更新指向需要操作的fiber的节点,当next=null则表明没有下一个工作单元了。
在beginWork中,next始终查找当前 fiber 节点的fiber.child。如果next为null,则表示当前 fiber 没有子节点。然后进入completeUnitOfWork工作。在completeUnitOfWork中,会执行completeWork函数。
function performUnitOfWork(unitOfWork: Fiber): void {
  let next;
  // 省略了一些判断代码
  next = beginWork(current, unitOfWork, renderLanes);
  if (next === null) {
    // 当找不到下一个工作单元后,就执行completeUnitOfWork的逻辑
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}
在completeUnitOfWork中会查询是否存在sibling,若不存在就直接回道parent了
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
  // If there is more work to do in this returnFiber, do that next.
  workInProgress = siblingFiber;
  return;
}
// Otherwise, return to the parent
completedWork = returnFiber;
workInProgress = completedWork;
基于以上分析,可以说 fiber 的遍历过程是先找child,如果child不存在再找sibling,若sibling也没有了就返回到parent。
这是后序遍历。
在beginWork阶段,除了查找子节点之外,还会根据标签的不同创建不同的Fiber节点。这里的 switch 的类型就是用的我们上面表格的类型。
// beginWork
// 根据tag执行不同的函数
switch (workInProgress.tag) {
  case IndeterminateComponent:
  case LazyComponent:
  case FunctionComponent:
  case ClassComponent:
  case HostRoot:
  case HostComponent:
  case HostText:
  case SuspenseComponent:
}
effect list
在 commit 节点时,需要找到所有带有 effectTag 的 Fiber 节点,并一次性执行 effectTag 的对应操作(创建、更新、删除)。
在 completeUnitOfWork 中,每个执行完 completeWork 且存在 effectTag 的 Fiber 节点都会保存在一条 effectList 的单项链表中。
effectList 中第一个 Fiber 节点保存在 fiber.firstEffect,最后一个节点保存在 fiber.lastEffect。
最终形成一条以 rootFiber.firstEffect 为起点的单项链表。
commit 阶段
commit 阶段主要工作分为三个阶段:
- before mutation 阶段(执行 DOM 操作之前)
- mutation 阶段(执行 DOM 操作)
- layout 阶段(执行 DOM 操作后)
遍历过程
将 jsx 转化为 fiber tree,每个节点有 child、sibling、return 属性,通过后序遍历 fiber tree。
任务执行过程
// 保存当前的处理现场
let nextUnitOfWork: Fiber | undefined; // 保存下一个需要处理的工作单元
let topWork: Fiber | undefined; // 保存第一个工作单元
function workLoop(deadline: IdleDeadline) {
  // updateQueue中获取下一个或者恢复上一次中断的执行单元
  if (nextUnitOfWork == null) {
    nextUnitOfWork = topWork = getNextUnitOfWork();
  }
  // 🔴 每执行完一个执行单元,检查一次剩余时间
  // 如果被中断,下一次执行还是从 nextUnitOfWork 开始处理
  while (nextUnitOfWork && deadline.timeRemaining() > ENOUGH_TIME) {
    // 下文我们再看performUnitOfWork
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork, topWork);
  }
  // 提交工作,下文会介绍
  if (pendingCommit) {
    commitAllWork(pendingCommit);
  }
}
参考文章
https://www.xiabingbao.com/post/react/jsx-element-fiber-rfztfs.html