【Vue】为什么在使用v-for的时候需要指定key属性?

238 阅读2分钟

这个问题涉及到了虚拟Dom的更新过程和策略。当渲染的数据发生改变时,会生成新的虚拟Dom树和老的虚拟Dom树进行比较,返回对Dom树的更新操作。

以列表渲染为例,如果数据项的顺序被改变,为了尽量减少Dom的渲染操作,Vue将不是移动DOM元素来匹配数据项的改变(更不是全部销毁重新渲染),而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素,即就地复用策略。 比如: 在BC中间插入一个新的节点,如果不指定key,在页面Dom的真实变更如下: 原来ACBDE的Dom其实没有变化,只是在最后增加一个Dom,将原来显示C的Dom显示的内容改为显示F,依次类推。就是说,原来显示C对应的Dom假设在浏览器中的ID是C_dom,更新后显示C的Dom对应的ID就变成D_dom了。这样的方式其实不是更新的最优解。

有一个严重的问题就是,v-for如果不指定key,如果列表不只是展示功能,而是有勾选选中功能时,更新前后会导致选中项的错误变化。比如下面这个例子,一个可勾选的列表,可以动态的在列表顶端增加项。(代码在文末提供) 在选中第二项(JAVA)后,增加一项被选中的项变成了JS,依然是列表的第二项! 换一种方式,我们把修改第二项的样式,然后增加顶部增加一项 得到的结果依然和前面类似。也就是说,Dom一些属性没有跟随列表的更新过程中随着Dom树的更新跟着对应的内容更新,而是绑定在Dom上。当为v-for增加key属性后,这样的“bug”消失了。增加key属性后,列表项能绑定Dom节点,diff算法能跟踪每个节点的身份,从而重用和重新排序现有元素。

值得注意的是,如果key使用列表的index绑定,可能达不到想要的效果。因为index在列表改变的时候一起改变了,达不到作为列表项唯一标识的目的。

<div v-for="(item, index) in list" :key="index" >

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute。建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提

以上内容根据其他博客整理,经过自己的验证,增加自己的思考。如果有错误欢迎指正。

附录

<template>
  <div id="app">
    <label >Range:</label><input type="text" v-model="count"><br>
    <label >Name:</label><input type="text" v-model="name">
    <input type="button" value="添加" @click="add">
    <div v-for="item in list" >
      <input type="checkbox">[{{item.count}}]——{{item.name}}
    </div>
  </div>
</template>
<script>
export default {
  name: 'ListRend',
  data () {
    return {
      count: '',
      name: '',
      list: [
        {count: 1001, name: 'JS'},
        {count: 1002, name: 'JAVA'},
        {count: 1003, name: 'C#'},
        {count: 1004, name: 'C++'},
        {count: 1005, name: 'C'}
      ]
    }
  },
  methods: {
    add () {
      let p = {
        count: this.count,
        name: this.name
      }
      this.list.unshift(p)
    }
  }

}
</script>