将由两个结构极为简单的实例向大家展示,使用index作为key出现的bug,和正确赋值key的区别所在。并对此进行分析。
list = ['a','b','c','d','e'] 复制代码
list.map((item, index) => { return <li key={index}>{item}</li> }) 复制代码
<ul> <li v-for="(item, index) in list" :key="index"> {{ item }} </li> </ul> 复制代码
这样写似乎不会报错?的确,这样写确实没有任何语法上的问题。
但是,一旦我们对遍历好的item项再做操作,结果与我们想象的将会大不相同。
我用React写一个结构简单的例子小伙伴们就秒懂了(●´∀`●):
用vue写原理也是一样的,我们不必局限于所使用的框架
代码如下:
class Items extends React.Component { constructor(props) { super(props) this.state = {list: ['a','b','c','d','e']} } render() { let list = this.state.list return ( <ul> {list.map((item, index) => ( <li key={index} onClick={this.deleteItem.bind(this, index)} > {item} </li> ))} </ul> ) } // 删除点击的item deleteItem(index) { let newlist = [...this.state.list] newlist.splice(index, 1) this.setState(()=>({list: newlist})) } } 复制代码
这时有小伙伴就说了,你弄了半天想说明啥,,这效果有问题吗?
我想说,这效果没问题,,,But!每次点击删除对应li标签后最终浏览器对dom的操作大有问题!
我们点击第一个item(文本为a)看似删除了它,但是实际上浏览器做了什么呢?
浏览器将最后一个item删除了!
你会想我明明看到a被删除了,接着是b被删除,再是c....
让我们一起来看右边的控制台。每次点击第一个li标签,发现dom结构中ul标签以及剩余的所有li标签都发生了闪烁!
浏览器竟然重新渲染了所有的li标签!
无论是vue还是react其内部都用虚拟Dom(VDom)机制来更新浏览器中真实的Dom。
如何判断新旧虚拟DOM的差异呢,这就用到了diff算法。
在此案例中当我们删除了第一个节点,那么react就会生成一个新的虚拟DOM(newVDom),为了问题简单化我们只针对列表部分进行分析。
<!-- 真正的虚拟DOM是js中的对象。 --> <!-- newVDom --> <ul> <li>b</li> <!-- key==0 --> <li>c</li> <!-- key==1 --> <li>d</li> <!-- key==2 --> <li>e</li> <!-- key==3 --> </ul> <!-- oldVDom --> <ul> <li>a</li> <!-- key==0 --> <li>b</li> <!-- key==1 --> <li>c</li> <!-- key==2 --> <li>d</li> <!-- key==3 --> <li>e</li> <!-- key==4 --> </ul> 复制代码
newVDom | oldVDom | 变化 |
---|---|---|
key=0 | key==1 | 文本改变 |
key=1 | key==1 | 文本改变 |
key=2 | key==2 | 文本改变 |
key=3 | key==3 | 文本改变 |
key=4 | 无 | 删除节点 |
在react中,只有被改变了的元素才会被重新渲染
1.diff将上面的所有比对的差异放入一个补丁包中,再将补丁包应用到真实DOM上。
2.原本我们每次点击只需要逐个删除列表中的第一个li节点
3.由于用index做key导致点击事件发生后,新产生的newVDOM中li的key被动态赋值,于是就有了上面对比错位,导致diff以为每个li都发生了改变,于是页面重新渲染了所有的列表项。
list.map((item, index) => ( <li key={item} onClick={this.deleteItem.bind(this, index)} > {item} </li> ) 复制代码
暂且让item成为key(当然在做项目时这仍不可取)。于是每个li都独有一个key,值分别为'a','b','c','d','e'。
可见每点击一次,生成相对应的newVDom就会少一个节点,进入diff算法进行新旧VDom的比较时就会得出结果:仅仅是删除了一个节点。再由newVDom映射到真实的Dom,只做了一个删除dom的操作,并没有重新渲染其他li,极大提升了性能。
无论是Vue还是React,当我们在做数组遍历批量生成子节点时,切记同层级的每个子节点的key值不能重复且不会发改变,否则将会产生不可预估的bug。