相比于早期的栈协调器,Fiber在虚拟DOM树的基础上构建了Fiber树。在Fiber树的每一个节点中存储了很多内容,包含但不限于如下内容:

  • stateNode:状态节点;
  • return:该节点的父级Fiber节点引用;
  • child:该节点的第一个子Fiber节点引用;
  • sibling:当前层级的下一个兄弟Fiber节点。

React首次渲染后会生成并暂存一个Fiber树(下文中将以current树指代这个Fiber树),它反映了当前真实的DOM树的结构信息,如下图所示。
image.png

当组件状态发生改变时,React会新生成一个对应的树来生成状态更新后的Fiber树并保存这些变化,称为WorkInProgress树。

Render阶段

在WorkInProgress树中,由根节点开始按照一定顺序为每一个Fiber进行变化对比:如果新旧节点没有变化,则复制current树上的相应节点至WorkInProgress树中;如果对比旧的节点有变化,则记录该变化并将变化推入effectList链表中。每完成一次对比,都检查一下当前帧是否还有剩余时间,如果有则进行下一个节点的对比工作,如果剩余时间为0,则标记当前节点并使用window.requestIdleCallback()将该节点注册至下一次浏览器空闲时的回调函数中,等下一次被唤起时继续进行或重新开始。

小知识:window.requestIdleCallback()是一个较新的浏览器API,用于在浏览器空闲时执行回调函数。一般情况下,回调函数会根据先进先出的队列模型,按照注册的顺序来执行。如果队列中的某些回调函数指定了执行时间,可能是为了提高总体执行效率而打乱顺序执行的次序。该API使开发者能在主事件循环内执行低的优先级操作,而不会影响更高优先级的事件(如动画和输入响应)。

在WorkInProgress树中,每一个节点对比完毕之后按照以下规则查找下一个节点:

  • 如果当前节点存在sibling,则以sibling指向的节点作为下一个工作单元;
  • 如果当前节点不存在sibling,但存在尚未完成对比的child,则以child指向的节点作为下一个工作单元;
  • 如果当前节点既不存在sibling,也不存在尚未完成对比的child,则以return指向的节点作为下一个工作单元;
  • 如果当前节点是HostRoot,则不存在下一个工作单元,完成当前操作后进入pendding- Commit阶段。

下图展示了WorkInProgress树的结构及它与current树的关系。
image.png

到此为止,Fiber协调器的第一个阶段结束。
Fiber协调器的第一个阶段被称为Render阶段或Reconciliation阶段。该阶段的任务默认为低优先级,可以被更高优先级的任务中断或暂停。该阶段得到标记了副作用的Fiber节点树。副作用表示在下一个阶段需要完成的工作。 :::tips 小知识:副作用(Side Effect)是指函数除了返回一个值之外,造成的其他影响,如修改外部变量、抛出异常、I/O操作和界面渲染等。上文中所说的副作用主要指对Fiber节点进行插入、更新和删除等。 ::: 第一个阶段包含的生命周期函数有componentWillMount()、componentWillReceiveProps()、getDerivedStateFromProps()、shouldComponentUpdate()、componentWillUpdate()及render()。
第一阶段的任务随时可能被中断或重来,可控性不高,建议开发者尽量不要在这几个生命周期中做副作用操作。

Commit阶段

接下来是Fiber协调器的第二阶段,被称为Commit阶段。此阶段React会将上一阶段收集到的effectList依次提交给真实的DOM操作,触发页面展示的改变。此阶段的任务优先级为同步,也就是说这一系列的DOM操作不能被其他任务中断。
第二阶段包含的生命周期函数有getSnapshotBeforeUpdate()、componentDidMount()、componentDidUpdate()及componentWillUnmount()。
第二阶段结束后,能反映真实DOM树结构的Fiber树是WorkInProgress树。而在协调的第一阶段,current树才对应真实的DOM树。所以在第二阶段结束时React会把current树和WorkInProgress树的指针对调,使其符合current树反映当前真实的DOM树的这一设定。
这意味着React可以对旧的对象(如这里的WorkInProgress树)进行复用,当下次需要使用WorkInProgress树时,React不需要重新构建它,只需要复制其中的键值即可。该技术被称为双缓存(Double Buffering),它节省了缓存分配和垃圾回收的时间,提高了协调的效率。

React Fiber小结

React Fiber可以使React应用变得更加流畅,但它不是万能的。以下情形造成的应用卡顿不会因为React Fiber的存在而变得更加流畅。

  • 生命周期内大量的计算任务:由于React Fiber是以Fiber节点为最小颗粒(即操作单元)的,无法在生命周期内对任务进行拆分,这些计算任务在执行中途不能被中断;
  • 大量真实的DOM操作:因为这部分任务在Fiber的第二阶段同步执行,无法被打断,过大的DOM操作压力只能由浏览器承担。

总体来讲,React Fiber通过将任务切片,以及采用合适的任务调度机制,解决了高优先级任务被阻塞的问题,使应用的展示和反馈更加顺畅,使复杂应用中的用户体验得到了极大的提升。

疑问

fiber为什么要用链表结构而不是数组?

因为链表新增删除不会改变原来的数据位置,数组会改变;因为每次只处理一个 fiber 节点,每次处理前都判断下,这样自然可以切换到别的 fiber 的处理;