理解React Fiber

567 阅读3分钟

这是我参与更文挑战的第13天,活动详情查看: 更文挑战

背景

Fiber是react的核心算法的重构。重构的核心目标是增加适用性以及包括动画,布局和手势。其目标包含:

  • 把可中断的任务拆分成小任务
  • 对正在执行的任务做重新的排序,优化执行顺序,利用上次的计算结果。
  • 在父子任务之间从容切换,以支持React执行过程中的布局刷新
  • 更好的支持error boundary
  • 支持render()返回多个元素

既然初衷是不希望JS不受控制地长时间执行(想要手动调度),那么,为什么JS长时间执行会影响交互响应、动画?

因为JavaScript在浏览器的主线程上运行,恰好与样式计算、布局以及许多情况下的绘制一起运行。
如果JavaScript运行时间过长,就会阻塞这些其他工作,可能导致掉帧。

Fiber的关键特性

  • 增量渲染(把渲染任务拆分成块,匀到多帧)
  • 更新时能够暂停,终止,复用渲染任务
  • 给不同类型的更新赋予优先级
  • 并发方面新的基础能力 增量渲染用来解决掉帧的问题,渲染任务拆分之后,每次只做一小段,做完一段就把时间控制权交还给主线程,而不像之前长时间占用。这种策略叫做cooperative scheduling(合作式调度)

fiber与fiber tree

React运行时存在3种实例:

DOM 真实DOM节点
-------
Instances React维护的vDOM tree node
-------
Elements 描述UI长什么样子(type, props)

Instances是根据Elements创建的,对组件及DOM节点的抽象表示,vDOM tree维护了组件状态以及组件与DOM树的关系

在首次渲染过程中构建出vDOM tree,后续需要更新时(setState()),diff vDOM tree得到DOM change,并把DOM change应用(patch)到DOM树

Fiber之前的reconciler(被称为Stack reconciler)自顶向下的递归mount/update,无法中断(持续占用主线程),这样主线程上的布局、动画等周期性任务以及交互响应就无法立即得到处理,影响体验

Fiber解决这个问题的思路是把渲染/更新过程(递归diff)拆分成一系列小任务,每次检查树上的一小部分,做完看是否还有时间继续下一个任务,有的话继续,没有的话把自己挂起,主线程不忙的时候再继续

增量更新需要更多的上下文信息,之前的vDOM tree显然难以满足,所以扩展出了fiber tree(即Fiber上下文的vDOM tree),更新过程就是根据输入数据以及现有的fiber tree构造出新的fiber tree(workInProgress tree)。因此,Instance层新增了这些实例:

DOM
    真实DOM节点
-------
effect
    每个workInProgress tree节点上都有一个effect list
    用来存放diff结果
    当前节点更新完毕会向上merge effect list(queue收集diff结果)
- - - -
workInProgress
    workInProgress tree是reconcile过程中从fiber tree建立的当前进度快照,用于断点恢复
- - - -
fiber
    fiber tree与vDOM tree类似,用来描述增量更新所需的上下文信息
-------
Elements
    描述UI长什么样子(type, props)

注意:放在虚线上的2层都是临时的结构,仅在更新时有用,日常不持续维护。effect指的就是side effect,包括将要做的DOM change

fiber tree上各节点的主要结构(每个节点称为fiber)如下:

// fiber tree节点结构
{
    stateNode,
    child,
    return,
    sibling,
    ...
}
return表示当前节点处理完毕后,应该向谁提交自己的成果(effect list)