Live Note

Remain optimistic

Fiber架构的实现原理

Fiber 的起源

最早的Fiber官方解释来源于2016 年 React 团队成员 Acdlite 的一篇介绍  (opens new window)

从上一章的学习我们知道:

React15及以前,Reconciler采用递归的方式创建虚拟 DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。

为了解决这个问题,React16将递归的无法中断的更新重构为异步的可中断更新,由于曾经用于递归的虚拟 DOM 数据结构已经无法满足需要。于是,全新的Fiber架构应运而生。

Fiber 的含义

Fiber包含三层含义:

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

Fiber 的结构

你可以从这里看到Fiber 节点的属性定义  (opens new window)。虽然属性很多,但我们可以按三层含义将他们分类来看

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// 作为静态数据结构的属性
this.tag = tag
this.key = key
this.elementType = null
this.type = null
this.stateNode = null

// 用于连接其他Fiber节点形成Fiber树
this.return = null
this.child = null
this.sibling = null
this.index = 0

this.ref = null

// 作为动态的工作单元的属性
this.pendingProps = pendingProps
this.memoizedProps = null
this.updateQueue = null
this.memoizedState = null
this.dependencies = null

this.mode = mode

this.effectTag = NoEffect
this.nextEffect = null

this.firstEffect = null
this.lastEffect = null

// 调度优先级相关
this.lanes = NoLanes
this.childLanes = NoLanes

// 指向该fiber在另一次更新时对应的fiber
this.alternate = null
}

作为架构来说

每个 Fiber 节点有个对应的React element,多个Fiber节点是如何连接形成树呢?靠如下三个属性:

1
2
3
4
5
6
// 指向父级Fiber节点
this.return = null
// 指向子Fiber节点
this.child = null
// 指向右边第一个兄弟Fiber节点
this.sibling = null

举个例子,如下的组件结构:

1
2
3
4
5
6
7
8
function App() {
return (
<div>
i am
<span>KaSong</span>
</div>
)
}

对应的Fiber树结构:

Fiber架构

这里需要提一下,为什么父级指针叫做return而不是parent或者father呢?因为作为一个工作单元,return指节点执行完completeWork(本章后面会介绍)后会返回的下一个节点。子Fiber节点及其兄弟节点完成工作后会返回其父级节点,所以用return指代父级节点。

作为静态的数据结构

作为一种静态的数据结构,保存了组件相关的信息:

1
2
3
4
5
6
7
8
9
10
// Fiber对应组件的类型 Function/Class/Host...
this.tag = tag
// key属性
this.key = key
// 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹
this.elementType = null
// 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName
this.type = null
// Fiber对应的真实DOM节点
this.stateNode = null

作为动态的工作单元

作为动态的工作单元,Fiber中如下参数保存了本次更新相关的信息,我们会在后续的更新流程中使用到具体属性时再详细介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 保存本次更新造成的状态改变相关信息
this.pendingProps = pendingProps
this.memoizedProps = null
this.updateQueue = null
this.memoizedState = null
this.dependencies = null

this.mode = mode

// 保存本次更新会造成的DOM操作
this.effectTag = NoEffect
this.nextEffect = null

this.firstEffect = null
this.lastEffect = null

如下两个字段保存调度优先级相关的信息,会在讲解Scheduler时介绍。

1
2
3
// 调度优先级相关
this.lanes = NoLanes
this.childLanes = NoLanes

注意

在 2020 年 5 月,调度优先级策略经历了比较大的重构。以expirationTime属性为代表的优先级模型被lane取代。详见这个 PR(opens new window)

如果你的源码中fiber.expirationTime仍存在,请参照调试源码章节获取最新代码。

总结

本节我们了解了Fiber的起源与架构,其中Fiber节点可以构成Fiber树。那么Fiber树和页面呈现的DOM树有什么关系,React又是如何更新DOM的呢?

我们会在下一节讲解。

参考资料

Lin Clark - A Cartoon Intro to Fiber - React Conf 2017