前言
上一篇文章介绍了如何搭建react调试环境。经过了艰苦的调试源码,这篇我们来讲讲React的基础Fiber以及Fiber架构。
Fiber的起源
标准的起源开头,凑点字数😄。在React15及以前,Reconciler采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。
那么新架构当然要解决这个问题啦,怎么解决呢?复杂的问题解决方案往往是简单的,既然是不能终端造成的,那将递归的无法中断的更新重构为异步的可中断更新,不就好了嘛(哦吼,真简单,你行你上啊 🐶)。于是,全新的Fiber架构应运而生。
React新架构
新架构分成三层:
- Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
- Reconciler(协调器)—— 负责找出变化的组件
- Renderer(渲染器)—— 负责将变化的组件渲染到页面上
相较于老架构,新架构中新增了Scheduler(调度器) ,我们来看看它到底是什么。
requestIdleCallback
先给大家科普下此API。
**
window.requestIdleCallback()**方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。请参考浏览器兼容性表格以得到在不同浏览器中适合使用的前缀。
看上图我们来看下requestIdleCallback的工作原理。一般60HZ的屏幕,一帧的时间是16.6ms。在此时间内,需要完成交互、触发定时器、事件执行、raf,然后开始渲染绘制,如果这些功能完成之后还有剩余时间,接下来就会执行requestIdleCallback的回调函数。那么关联上面说的异步的可中断更新,大家想想是不是可以用这个方法去实现。
当然了,React确实也是这样实现的。但是由于兼容性以及稳定性这个API并不能直接使用,所以React自己实现了requestIdleCallbackpolyfill,并且提供了多种调度优先级供任务设置。
当requestIdleCallback将任务交给Reconciler后,也就是fiber架构后,每次fiber循环都会调用判断当前是否有剩余时间。
Fiber
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode
) {
// 静态数据结构
// 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; // 真实dom
// 用于连接其他Fiber节点形成Fiber树
// 指向父节点Fiber
this.return = null;
// child:指向子节点Fiber
this.child = null;
// sibling:指向右边的兄弟节点Fiber
this.sibling = null;
this.index = 0;
this.ref = null;
// 保存本次更新造成的状态改变相关信息
// 函数式组件&&Fiber
// 更新/新Fiber节点时用来传递props
this.pendingProps = pendingProps;
// 一次渲染完成后的props
this.memoizedProps = null;
// 所有effect
this.updateQueue = null;
// 调用hook时产生的hook对象,hook之间以.next相连形成单向链表。
this.memoizedState = null;
// 组件中通过useContext获取到的上下文
this.dependencies = null;
this.mode = mode;
// Effects
// 节点本身的保存的操作依据与Fiber节点的子树的操作依据。
this.flags = NoFlags;
// 节点本身的保存的操作依据与Fiber节点的子树的操作依据。
this.subtreeFlags = NoFlags;
// 待删除的子节点
this.deletions = null;
// 调度优先级相关
this.lanes = NoLanes;
this.childLanes = NoLanes;
// 指向该fiber在另一次更新时对应的fiber
this.alternate = null;
...
}
这就是Fiber数据结构,其实是按照三层分的静态数据结构、fiber树结构、 状态改变相关信息三个维度。
Fiber架构(Fiber树)
每个Fiber节点有个对应的React element,多个Fiber节点是连接形成树,就组成了Fiber架构的基本结构。
最主要的就是下面三个属性:
// 用于连接其他Fiber节点形成Fiber树
// 指向父节点Fiber
this.return = null;
// child:指向子节点Fiber
this.child = null;
// sibling:指向右边的兄弟节点Fiber
this.sibling = null;
举个栗子:
function App() {
return (
<div>
A1
<span>B1</span>
</div>
)
}
对应的Fiber树结构:
这里要提一下,这个图其实很好的反应出了 render阶段的“递“过程和“归”过程。首先从
根开始向下深度优先遍历。为遍历的每个节点创建自身fiber以及创建子fiber并建立联系,当遍历到叶子节点时就会进入“归”过程。当某个节点执行完“递“过程,如果其存在兄弟节点,会进入其兄弟的“递”阶段。 如果不存在兄弟,会进入父级的“归”过程。“递”和“归”阶段会交错执行直到“归”到根。至此,render阶段的工作就结束了。
本节我们了解了Fiber以及Fiber树数据结构,那么Fiber架构具体是什么流程?下一章我们会详细讲解。