我们在写React组件时[].map会提示我们需要为组件添加一个key。那么这个key有什么必要性呢?
React Diffing 算法
当对比两棵树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态
1、对比不同类型的元素
当根节点为不同类型的元素时,React 会卸载原有的树并且建立起新的树。举个例子,当一个元素从 变成 <img>,从 <Article> 变成 <Comment>,或从 <Button> 变成 <div> 都会触发一个完整的重建流程。
当卸载一棵树时,对应的 DOM 节点也会被销毁。当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中。所有与之前的树相关联的 state 也会被销毁。根节点以下的组件也会被卸载,它们的状态会被销毁。
比如,当比对以下更变时:
<div>
<Counter />
</div>
<span>
<Counter />
</span>
React 会销毁 Counter 组件并且重新装载一个新的组件。
2、对比同一类型的元素
当对比两个相同类型的元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。比如:
<div className="before" title="stuff" />
<div className="after" title="stuff" />
通过对比这两个元素,React 知道只需要修改 DOM 元素上的 className 属性。
在处理完当前节点之后,React 继续对子节点进行递归。
默认情况下,当递归 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>Duke</li> 和 <li>Villanova</li>,而是会重建每一个子元素。这种情况就会带来性能问题。
Keys
为了解决上述问题,React 引入了 key 属性。当子元素拥有 key 时,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>
key 不需要全局唯一,但在列表中需要保持唯一。
尽量避免使用数组中的下标作为 key,当基于下标的组件进行重新排序时,组件 state 可能会遇到一些问题。由于组件实例是基于它们的 key 来决定是否更新以及复用,如果 key 是一个下标,那么修改顺序时会修改当前的 key,导致非受控组件的 state(比如输入框)可能相互篡改,会出现无法预期的变动。
React 可以在每个 action 之后对整个应用进行重新渲染,重新渲染表示在所有组件内调用 render 方法,但是这不代表 React 会卸载或装载它们。React 只会基于以上提到的规则来决定如何进行差异的合并。
所以,综上所述
Key 应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key(比如通过 Math.random() 生成的)会导致许多组件实例和 DOM 节点被不必要地重新创建,这可能导致性能下降和子组件中的状态丢失。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。