使用 v-for 时,如果每次迭代的元素是一个对象,而不是一个简单的值,那么建议将数据模型中具有唯一性的属性绑定到 DOM 元素的 key 属性上。
具体的原理:
当 Vuejs 更新使用 v-for 渲染的列表时,默认使用“就地更新”的策略。如果数据项的顺序发生了改变,Vue.js 不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位詈得到正确渲染。假设一个列表对应的数据模型在数组的末尾增加了一个新元素,Vue.is 的做法就是把这个元素放到数组的最后,这自然是最方便的做法。但是,如果数据模型在数组的开头插入了一个元素,那么默认情况下,Vue.is 依然会在数组的最后添加一个元素,然后从第一个元素开始逐个更新,以保持与数据模型的一致。
但是,这种做法在某些情况下就会产生一些问题。下面通过一个例子来进行说明,假设我们想要开发一个微型的“待办事项”(Todo List)页面。我们将在这个页面上列出一些待办事项,用户既可以添加新的待办事项,也可以在某个待办事项的前面打勾,表示已经完成该事项。
<body>
<div id="app">
<div>
<input type="text" v-model="todo">
<button v-on:click="onClick">添加 </button>
</div>
<ul>
<li v-for="item in list"><input type="checkbox">{{item.todo}}</li>
</ul>
</div>
<script>
var vm = new vue({
el:'#app',
data:{
todo:'',
newId:5,
list: [
{id:1,todo:'去健身房健身'},
{id:2,todo:'去饭店吃饭'},
{id:3,todo:'去银行存钱'},
{id:4,todo:'去商场购物'}
]
},
methods:{
onClick(){
// unshift()会在数组的开头插入元素
this.list.unshift({ id: this.newId++, todo: this.todo });
this.todo ='';
}
}
})
</script>
</body>
项目列表都是“就地更新”的,复选框仍然保持着原来的状态,而解决这个问题的方法 就是对li元素绑定key属性--在v-for后面添加v-bind:key:
<li v-for:"item in list" v-bond:key="item.id">
<input type = "checkbox"> {{item.todo}}
</li>
这就是对迭代的元素绑定 key属性的作用所在,可以简单理解为,将 key 属性绑定到数据模型中具有唯一性的属性之后,整个迭代素和数据模型就一一对应了,新元素会按顺序插人原来的列表中,而不是“就地更新”
使用 v-for 绑定对象时的原理
Vue.js 的内部实现了一套虚拟 DOM,也就是存中的 DOM 结构,可在必要时根据虚拟的 DOM 结构更新真正的页面 DOM 结构,因为每次虚拟DO发生变化时,一般只是局部有变化,这显然不能简单粗暴地更新整个页面的所有元素,而应该尽可少地更新页面元素,只有这样,效率才会越高。
在不指定 key 属性的时候,Vue.js 会最大限度地减少对元素的更改并且尽可能尝试“就地”修改,同时尽可能复用相同类型元素的算法;而在使用 key 属性时,Vue.js 会基于 key 属性的变化重新排列元素的顺序,并且移除 key 属性不存在的元素。
在没有绑定 key 属性之前列表元素的更新方式与绑定之后不同。 Vue.js 这样做的原因是,如果没有为每一个列表元素指定可以唯一识别这个列表元素的“标识”的话,列表元素和数据模型就无法形成一一对应的关系,Vue.js 的引擎也就无法识别出新加入的列表元素应该插人的位置,因此默认采用的办法就是把新加人的元素放到最后,然后从开头依次更新到与数据模型一致的状态,此时用户选中的复选框的位置将不会发生变化。
而一旦在 Vue.js 的列表中对 key 属性绑定了“唯一标识”以后,Vue.js 就可以明确地知道新元素应插人什么位置了。唯一标识一般使用对象的 id 等属性,用数据库中的术语来说,就是对象的“主键。