前言

将由两个结构极为简单的实例向咱们展现,运用index作为key出现的bug,和正确赋值key的区别地点。并对此进行分析。

假设有一个数组:

list = ['a','b','c','d','e']

初学者们在写rS O 3 F o |eact时对数组进行遍历时喜欢n * + Z这样写:

list.map((iR U t Ttem, index) => {
return &lR E h g W c p mt;li key={index}>{item}</liR , { | e>
})

写vue时对数组遍历:

<ul>
&ls } }t;li v-for="(i) N v = * Q M D Utem, index) in li6 . c z k b Qsd ` } { v E 1 / Wt" :key="index">
{{ item }}
</li>
</ul>

这样写好像不会报错?的确,这样写的确没有任何语法上的问题。
但是,一旦咱们对遍历好的item项再做操作,成果与咱们幻想的将会大不相同。

我用React写一个结构简单的比如小伙伴们就秒懂了(●∀`●):

用vue写原理也是相同的,咱们不用局限于所运用的框架

代码如下P [ t c F ^ P

class Items extK ( m sends React._ M t O | - { c 1CD _ Z i c n a G `omponent {
construcV ~ Htor(props) {
super(props)
this.state = {list: ['a','b','c','d','e']}
}
render() {A } Q X t - @
let list = thiT I ! _s.state.list
return (
<ul>
{list.map((item, index) h ( } T t } : c => (
<li
key={index}
onC~ ) o ?lick={tP J ` L o o a mhis.deleteItem.bind(this, index)K = A q z Q 0 i =}
>
{item}
</li>
))}
</ul>
) = ) ] W 3 V Z
}
// 删去点击的item
deleteItem(index) {
let newlist = [...this.state.list]
newlist.s/ D h q 0 Z : aplt R $ u 4 # _ &ice(inde$ * 0 V g Dx, 1)
this.setState(()=>({list: newlv 6 ?ist6 ] A b } V s}))
}
}

这些代码会生成这样一个列表:

轻松理解为什么不用Index作为key

咱们在代码中给每个item绑定了一个deleteIt @ E qem事情,即点击某个item便会删y j z T w 去对应的item。

好了,咱们演示一下点击的作用:

轻松理解为什么不用Index作为key

这时有小伙伴就说了,你弄了半o q D响想阐明啥,o s {,这作用有问题吗?
我想说,这作用没问题,,,But!每次点击删去对应li标签后终究浏览器对dom的操作大有问题!

咱们点击第一个item(文本为a)看似删去了它,但是实际上浏览器做了什么呢?
浏览器将最终一个item删去了!

你会想我分明看到a被删去了,接着是b被删去,再是c….

让咱们一同来看右边的控制台。每次点击第一个li标签,发现dom结构中ul标签以及剩余的一切li标签都发生了闪耀!

浏览器竟然从头烘托了一切的li标签!

点击删去一个节点后Reat _ b Dct的内部操作:

无论是^ } P Q ` @ V +vue仍是react其内部都用虚拟Dom(VDom)机制来更新浏览器中实在的Dom。

如何判 c a & R n K断新旧虚拟DOM的差异呢,这就用; q –到了diff算法。

在此事例中当咱们删去了第一个节点,那么k f e q : @ y |react就会生成一个新的虚拟DOM(newVDop 4 R % u l 2 =m),为R w `了问题简j V F F L单化咱们只针对列~ c i C b @ P表部分进行分析。

<!-- 真正的虚拟DOM是js中的目标。 -->
<!-- newVDom -->
<ul>
<li>b</li>  <!-- k5 * N : G + ; _ey==0 -->
<li&X N p x - # ,gt;c</li>  <!-- key==m L t b % ,1 -->
<li>d</li>  <!-- key==2 --&g} # J # 8 M b It;
<li>e</li>  <!-- key==3 -->
</ul>
<!--  oldVDom -->
<ul>
<li>a</li>  <4 U h ? : 6 C :!-- key==0 -->
&l9 { qt;li>b</li>  <!-- key==1 -->
<li> S Dc</li>  &l{ J f j )t;!-- key==2 --9 M E ; o K 6 I>
<li>* q Z l c g { A;d</li>  <!-- key==3 -->
<li>e</li>  <O ] z } d i!-- key==4 --&}  ` 6 r d M (gt;
</ul>

diff算法将newVDom与改动前的Dom结构(oldVDom)进行比较,咱们找到key值相同的li标签,并进行自上至下逐一比照。比照发现:

newVDom oldq ( $ e ? z Z @ VDom 改动
key=0 key==1 文本改动
key=1 key==1 文本改动W . n 7 k
key=2 key==2 文本改动
key=3 key==3 文本改动
key=4 ` 4 , C N去节点
  • key为0的li标签文本由原先的a变为了b
  • key为1的li标签文本由原先的b变为{ y I N了c
  • key为2的li标签文本由原先的c变为了d
  • key为3的li标签文本由原先的d# # J b _ W变为了e
  • 最终ke% ` # x B v 1y为4的标签被移除

在react中,只要被改] y q 7 j K动了的元素o 5 c才会被从头烘托

1.diff将上面的一切比对的差异d H B放入一个补丁包中,再将补丁包应用到实在DOM上。
2.本来咱们每次点击只需要逐一删去列表中的第一个li节F Z P T & ! E H z
3.因为用index做key导致点击事情发生后,新发生的neT a y j N ) *wVDOM中li的key被动态赋值,于是就有了上面比照错位,导致diff以为每个li都发生了改动,于是页面从头烘托了一切的列表项。

尝试~ A ? b U让每个item拥有共同的key而不是下标

修正代码:

list.map((item, index) => (
<liK D S + R
key={item}
onClick={this.deleteItem.bind(this, index)}
>
{item}
</li>
)

暂且让item成为key(当然在做项目时这仍不行% $ 4 – L { : 4取)。于是每个li都独有一个key,值分别为’a’,’b’,’c’,’d’,’e’。

再次演示:

轻松理解为什么不用Index作为key

可见每点击一次,生成相对应的newVDom就会少一个节点,进入diff算法进行新旧VDom的比较时就会得出成果:仅h s @ ` [ i h V h仅是删去了一个节点。再由newVDom映射到实在的Dom,只做了一个删去dom的操作,并没有从头烘托其他li,极大提升r i Y J ( p V了功能。

小结

无论是Vue仍是React,当咱们在做数组遍历批量生成子节点时,切记同层级的每# c s e + x个子节点的key值不能重复且不会发改动,否则将会发生不行预估的bug。