在了解React的diffing算法之前,必先要知道React的diffing算法解决了什么问题,这就必要聊到传统的diffing算法了。
传统diffing算法
传统的diffing算法会通过循环递归对节点依次作对比,如果一棵树有n个节点,那么即使最先进的算法,该算法的复杂度为O(n³)
如上图所示,一个节点的更新,每一个节点都会对比,开销很大。如果React采用这个算法,那么它的更新性能会变得非常低效。
React diffing的优化策略
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 针对元素结构,可以通过key来指定哪些节点需要更新。
对比不同类型的节点
<div>
<Counter />
</div>
<span>
<Counter />
</span>
当div元素变成span元素后,React会直接卸载原有的树并且建立新的树。
对比同一层级的节点的变动
对于处于同一层级的节点,React diff提供了三种节点操作:插入,移动,删除。
在子元素列表末尾新增元素时,更新开销比较小。比如:
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
如果只是简单的将新增元素插入到表头,那么更新开销会比较大。比如:
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React并不会意识到应该保留<li>Duke</li> 和 <li>Villanova</li>,而是会重建每一个子元素。这种情况会带来性能问题。
keys的优化
当对同一层的子元素进行操作时,比如更新某个元素或是在开头或者中间插入元素时,可能会和每一个元素比较,重建每一个元素。有很大的性能问题。为了解决上述的问题,React引入了key属性。
以下示例在新增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知道只有带着'2014'key的元素时新元素,带着'2015'以及'2016'key的元素仅仅是移动了。
key的注意事项:
- 在最后位置插入数据,有无key意义并不大
- 在最前面插入数据这种做法,在没有key的情况下,所有的子元素(这里是li)都需要修改
- 当子元素拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素
- key应该是唯一的
- key不要使用随机数(随机数在下一次render时,会重新生成一个数字)
- 使用index作为key,对性能是没有优化的