React Fiber架构
从 React设计理念 和 React架构 中我们知道,在 v15
版本 Reconciler
采用递归的方式更新虚拟 DOM,这会导致什么问题呢?
由于递归过程是不能中断的,如果组件树的层级很深,递归更新时间超过了一帧,用户交互就会卡顿,

为了解决这个问题,v16
将递归的无法中断的更新重构为异步的可中断更新。
由于曾经用于递归的虚拟 DOM 数据结构已经无法满足需要,于是全新的 Fiber 架构应运而生。
那到底什么是 Fiber ?Fiber 只是一个架构吗?为什么说 Fiber 同时也作为静态的数据结构和动态的工作单元?
什么是 Fiber
React 团队的核心成员 Andrew Clark 在 2016 年的一次演讲 What's Next for React — ReactNext 2016 中第一次提到 Fiber,
这次演讲后来被整理为一篇介绍 React Fiber Architecture ,Fiber 作为 React 新版本的一种核心架构被正式提出。
A description of React's new core algorithm, React Fiber
在实现上,Fiber 对应了 DOM 树中的一个节点,我们可以从以下三个角度解读 Fiber:架构、数据结构和工作单元。
Fiber Architecture
作为架构而言,v15
的 Reconciler
采用递归的方式实现,数据保存在递归调用栈中,叫做 Stack Reconciler
,
v16
的 Reconciler
基于 Fiber 节点实现,被称为 Fiber Reconciler
,支持可中断的异步更新,任务支持切片,

Fiber Data Structure
Fiber 也是一种数据结构,我们可以从源码找到 Fiber节点的属性定义(关键的属性已经添加了注释,可以结合注释来看),
function FiberNode(
this: $FlowFixMe,
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode
) {
// Fiber 对应组件的类型 Function/Class/Host...
this.tag = tag;
// Diffing 需要的 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 节点
this.return = null;
// 指向子 Fiber 节点
this.child = null;
// 指向兄弟 Fiber 节点
this.sibling = null;
this.index = 0;
this.ref = null;
this.refCleanup = null;
// 保存本次更新的状态改变相关信息
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// 保存本次更新会造成的DOM操作
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
// 调度优先级相关
this.lanes = NoLanes;
this.childLanes = NoLanes;
// 指向该 Fiber 在另一次更新时对应的 Fiber
this.alternate = null;
}
Fiber 作为静态的数据结构,保存了组件的 tag
、key
、type
、stateNode
等相关信息。
Fiber Work Unit
Fiber 同时也是一个动态的工作单元,从 FiberNode
的定义中我们发现,
Fiber 保存了本次更新的状态改变相关信息,会造成的 DOM 操作(副作用 effect)以及调度优先级相关的信息。
Fiber 之所以可以作为 Work Unit,还与其工作原理有关,在正式介绍之前,我们先来了解一个 DOM 更新的技术:双缓存。
双缓存
双缓存(Double Buffering)是一个广泛应用于网络传输,图形渲染和内存读取优化等场景的技术,
我们以大家比较熟悉的渲染场景为例,假设我们需要用 canvas
绘制一个动画,然后显示到屏幕上,通常我们会在显示缓存区进行绘制,绘制每一帧前都会调用 ctx.clearRect
清除上一帧的画面,如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏,

为了解决这个问题,我们可以在自定义缓存区中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,这样就可以省去了两帧替换间的计算时间,避免白屏到出现画面的闪烁。
这种在内存中构建并直接替换的技术就是双缓存。
React Fiber 就是利用了双缓存技术来完成 “Fiber树” 的创建和替换,从而提高性能,具体是怎么实现的呢?
Alternate
React 中最多会同时存在两颗 Fiber 树,每次状态更新都会产生新的 workInProgress Fiber
树
- currentFiber,当前屏幕上显示内容对应的 Fiber 树
- workInProgressFiber,正在内存中构建的 Fiber 树
它们之间通过 alternate
属性连接,
currentFiber.alternate === workInProgressFiber; // true
workInProgressFiber.alternate === currentFiber; // true
React 应用的根节点通过使 current
指针在不同 Fiber 树的 rootFiber
间切换来完成 currentFiber
树指向的切换,
我们可以以组件 mount/update
的流程为例,了解 Fiber 树的构建和更新过程,考虑如下例子,
function App() {
const [num, add] = useState(0);
return (
<p onClick={() => add(num + 1)}>{num}</p>
)
}
ReactDOM.render(<App/>, document.getElementById('root'));
mount
首次执行 ReactDOM.render
会创建 fiberRoot
和 rootFiber
,其中,
fiberRoot
是整个应用的根节点rootFiber
是<App/>
所在组件树的根节点
此时,fiberRoot
的 current
指针指向当前的 Fiber 树,

接着会进入 render
阶段,React 会解析组件返回的 JSX
并在内存中依次创建 Fiber 节点连接成 Fiber 树,内存中完成构建的 Fiber 树叫做 workInProgress Fiber
树,这个过程中 React 会尝试复用 current Fiber
树中已有的 Fiber 节点内的属性,

然后是 commit
阶段,右侧已经构建完的 Fiber 树会替换掉当前的 Fiber 树,渲染到页面,

update
我们来接着讨论更新阶段,假设我们点击 p
节点触发状态改变,num
的值从 0
变为 1
,
每一次更新 React 都会开启一次新的 render
阶段并构建一棵新的 workInProgress Fiber
树,
和 mount
时一样,workInProgressFiber
的创建会尝试复用 currentFiber
节点,这个过程就是 Diffing
算法,

render
阶段完成后接着会进入 commit
阶段,workInProgressFiber
替换 currentFiber
完成渲染,

小结
Fiber 作为 React 新版本的核心架构,在实现可中断的异步更新中至关重要,
通过上述介绍,我们了解到 Fiber 不止作为架构,同时也是静态的数据结构和动态的工作单元,
Fiber 架构的核心是双缓存技术,其创建和更新的过程伴随着 DOM 的更新。
参考链接
- Lin Clark - A Cartoon Intro to Fiber - React Conf 2017
- React Fiber Architecture
- React技术揭秘
- Wikipedia, Multiple buffering
写在最后
本文首发于我的 博客,才疏学浅,难免有错误,文章有误之处还望不吝指正!
如果有疑问或者发现错误,可以在评论区进行提问和勘误,
如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
转载自:https://juejin.cn/post/7194719984143056952