1. Diffing 算法
React
是基于虚拟DOM
树的,也就是要对比原来的结构和预期更新后的树结构。那么这个过程是怎么完成的,这里做一个简单的记录。
- 从根节点开始比较,如果根节点为不同类型的元素时,
React
会拆卸原有的树并且建立起新的树。也就是会重建整个树结构。 - 如果为相同类型的
React
元素时,React
会保留DOM
节点,仅比对及更新有改变的属性。 - 然后递归的完成对对子节点的对比操作;
而,为什么我们在使用数组map
遍历的时候,必须指定一个key
属性?
其实,这也是出于重建每个元素所带来的开销问题。比如下面的案例:
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
在没有指定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
不需要全局唯一,但在列表中需要保持唯一。
下面,就上述两个案例来做一个简单测试:
class DiffTest extends Component {
constructor() {
super();
this.state = {
items: [
{
id: "0001", name: '张三'},
{
id: "0002", name: '李四'},
{
id: "0003", name: '王五'}
]
}
}
handleClick = () => {
const {
items} = this.state
let item = {
id: Date().toString(), name:"新增"}
this.setState({
items: [item,...items]})
}
render() {
const {
items} = this.state
return (
<div>
{
items.map((item, index) => {
return <li key={
index}>{
item.name} <input placeholder='请输入'/></li>
})
}
<button onClick={
this.handleClick}>添加一个Item</button>
</div>
);
}
}
注意到这里指定的key
值数据的下标位置,即:
<li key={index}>{item.name} <input placeholder='请输入'/></li>
这个案例的效果就为,当我们新增一个Item
的时候,其实并没有复用相同的Item
元素,而是对其所有均进行了重建操作。直观的可以从下面的运行图看出:
输入的内容没有对应的保存在“张三”一行,所以所有的元素其实都发生了重建。相反的,如果将key
指定为预定义的一个id
字段,即:
<li key={item.id}>{item.name} <input placeholder='请输入'/></li>
此时的效果就是,输入的内容对应的在“张三”一行,这里就不再给出运行图。
2. 注意
Key
应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key(比如通过Math.random()
生成的)会导致许多组件实例和DOM
节点被不必要地重新创建,这可能导致性能下降和子组件中的状态丢失。- 这个
key
不需要全局唯一,但在列表中需要保持唯一。