React的diffing算法

127 阅读2分钟

在了解React的diffing算法之前,必先要知道React的diffing算法解决了什么问题,这就必要聊到传统的diffing算法了。

传统diffing算法

传统的diffing算法会通过循环递归对节点依次作对比,如果一棵树有n个节点,那么即使最先进的算法,该算法的复杂度为O(n³)

1670578367570.png

如上图所示,一个节点的更新,每一个节点都会对比,开销很大。如果React采用这个算法,那么它的更新性能会变得非常低效。

React diffing的优化策略

  1. 同层节点之间相互比较,不会跨节点比较
  2. 不同类型的节点,产生不同的树结构
  3. 针对元素结构,可以通过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,对性能是没有优化的