前言
React实现可以粗划为两部分:reconciliation(diff阶段)和 commit(操作DOM阶段)。在 v16 之前,reconciliation 简单说就是一个自顶向下递归算法,产出需要对当前DOM进行更新或替换的操作列表,一旦开始,会持续占用主线程,中断操作却不容易实现。当JS长时间执行(如大量计算等),会阻塞样式计算、绘制等工作,出现页面脱帧现象。所以,v16 进行了一次重写,迎来了代号为Fiber的异步渲染架构。
Fiber
Fiber核心是实现了一个基于优先级和requestIdleCallback的循环任务调度算法。它包含以下特性:(参考:fiber-reconciler)
reconciliation阶段可以把任务拆分成多个小任务
reconciliation阶段可随时中止或恢复任务
可以根据优先级不同来选择优先执行任务
从其特性可看出,Fiber核心是更换了reconciliation阶段的运作。那么,问题来了:
为什么对reconciliation阶段进行拆分,commit阶段呢?
reconciliation阶段包含的主要工作是对current tree 和 new tree 做diff计算,找出变化部分。进行遍历、对比等是可以中断,歇一会儿接着再来。
commit阶段是对上一阶段获取到的变化部分应用到真实的DOM树中,是一系列的DOM操作。不仅要维护更复杂的DOM状态,而且中断后再继续,会对用户体验造成影响。在普遍的应用场景下,此阶段的耗时比diff计算等耗时相对短。
所以,Fiber选择在reconciliation阶段拆分
如何拆分呢?
首先,我们可以通过 A Cartoon Intro to Fiber中的一张图来看:
![image 640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/37ad13f711698b29c689cdb862821a5a.jpeg)
1.用户调用ReactDOM.render传入组件,React创建Element树;
2.在第一次渲染时,创建vdom树,用来维护组件状态和dom节点的信息(如List/Button/Item等)。当后续操作如render或setState时需要更新,通过diff算出变化的部分;
3.根据变化的部分更新vdom树、调用组件生命周期函数等,同步应用到真实的DOM节点中。
在第二阶段,Fiber是把render/update分片,拆解成多个小任务来执行,每次只检查树上部分节点,做完此部分后,若当前一帧(16ms)内还有足够的时间就继续做下一个小任务,时间不够就停止操作,等主线程空闲时再恢复。
这种停止/恢复操作,需要记录上下文信息。而当前只记录单一dom节点的vDom tree 是无法完成的,
Fiber引入了fiber tree,是用来记录上下文的vDom tree,可以理解为升级版的钢铁侠。
fiber tree上一个节点的结构大致有:
export type Fiber = {
tag: TypeOfWork, // 类型
type: 'div',
<