本篇文章来自官网的学习,如果对官网的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对性能的影响。