React Filber架构前置知识

1,015 阅读4分钟

本篇文章来自官网的学习,如果对官网的Diff已经掌握的同学可以跳过。

Fiber架构设计动力

我们知道React会有两棵树。这两棵树会交替运行,进行UI渲染。官网也给出了两个树的大概生成流程。

  • 在某一时间节点调用React的 render() 方法,会创建一颗由 React元素组成的树。
  • 在下一次state或props更新时,相同的render()方法会返回一颗不同的树。
  • React 需要基于着两棵树的差别来判断如何有效的更新 UI 以保证当前的 UI 与最新的树保持同步。

也就是说,React 在 update 阶段通过render()会返回一颗新的虚拟DOM树,而在刚开始的mount阶段通过render()生成一个起始的虚拟DOM树。总共会有两棵树。

如何生成将一颗树转换成另一颗树的最小操作数呢?哪怕是在最前沿的算法中,复杂度也是O(n 3 ),n代表树中元素的数量。假如有1000个元素,那需要计算的量级将会是10亿,这是不被允许的。所以,React 在以下两个假设中提出了一套 O(n) 的算法:

  • 两个不同类型的元素,会产生不同的树;
  • 开发者可以通过 key prop 来暗示哪些子元素在不同渲染下能够保持稳定;

通过这样的设计,产生了Diff算法。

Diff

当对比两棵树的时候,React 会首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态。

对比不同类型元素

当根节是为不同类型元素的时候,React 会拆卸原有的树构建新的树。

举个🌰:当元素<a>变成<img>就会触发一个完整的构建流程。

当卸载一颗树的时候,对应的DOM节点也会被销毁。组件实例将会执行 componentWillUnmount()方法。

当建立一颗新的树时,对应的DOM节点会被创建并插入到DOM中。组件实例将执行componentWillMount()方法,紧接着就执行了componentDidMount()方法。所以跟之前的树相关联的 state 也会取消。

举个🌰:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

div元素变成span元素,Counter 组件会被卸载,然后重新创建一个新的。

对比同一类型元素

当对比两个相同类型的 React 元素时,React会保留DOM节点,仅对比更新有改变的属性。

举个🌰:

<div className="before" title="stuff" />

<div className="after" title="stuff" />

对比这两个元素,React知道只是修改了className属性。并不会将div元素卸载再重新构建。

对比同类型的组件元素

当一个组件更新时,组件的实例保持不变,这样state在跨越不同的渲染时保持一致。

React将更新该组件实例的props,达到跟最新的元素保持一致,并且调用该实例的componentWillReceiveProps()和componentWillUpdate()方法。

下一步,调用render()方法,diff 算法将在之前的结果以及新的结果中进行递归。

对子节点进行递归

在默认情况下,当递归DOM节点子元素时,React会同时遍历两个子元素的列表;当产生差异时,生成一个mutation。

举个🌰:

在子元素末尾新增元素时,更变开销比较小。比如:

<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React 会先匹配 <li>first</li> 对应的树,然后匹配第二个元素<li>second</li>对应的树,最后插入第三个元素<li>third</li>

如果在列表的头部插入会很影响性能,开销会比较大。比如:

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React会发现新的<li>Connecticut</li>子元素与旧的<li>Duke</li>不同,结果就会卸载之后重新构建。这样对比,对每个子元素mutate。

Keys 在 Diff 中的作用

为了解决以上对每个子元素mutate的问题,React 支持 key 属性。当子元素拥有key的时候,React使用key来匹配原有树上的子元素,以及最新树上的子元素。

看下面这个例子:

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

现在 React 知道<li key="2014">Connecticut</li>这个子元素是新增的,其他的两个子元素是移动了。

这也是为什么我们的key不要使用index的原因,可能存在删除/新增列表中的一个元素,从而导致性能问题。

总结

  • fiber设计的架构动力--算法的时间复杂度导致需要优化
  • Diff算法的对比,DOM的卸载于重新构建的选择。
  • key对性能的影响。