咱们在写 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 节点被不必要地从新创立,这可能导致性能降落和子组件中的状态失落。