Vue中key的作用
key
的特殊attribute
主要用在Vue
的虛擬DOM
演算法,在新舊Nodes
對比時辨識VNodes
。如果不使用key
,Vue
會使用一種最大限度減少動態元素並且儘可能的嘗試就地修改、複用相同型別元素的演算法,而使用key
時,它會基於key
的變化重新排列元素順序,並且會移除key
不存在的元素。此外有相同父元素的子元素必須有獨特的key
,重複的key
會造成渲染錯誤。
描述
首先是官方文件的描述,當Vue
正在更新使用v-for
渲染的元素列表時,它預設使用就地更新的策略,如果資料項的順序被改變,Vue
將不會移動DOM
元素來匹配資料項的順序,而是就地更新每個元素,並且確保它們在每個索引位置正確渲染。這個預設的模式是高效的,但是隻適用於不依賴子元件狀態或臨時DOM
狀態的列表渲染輸出,例如表單輸入值。為了給Vue
一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一 key attribute
,建議儘可能在使用v-for
時提供key attribute
,除非遍歷輸出的DOM
內容非常簡單,或者是刻意依賴預設行為以獲取效能上的提升。
簡單來說,當在列表迴圈中使用key
時,需要使用key
來給每個節點做一個唯一標識,diff
演算法就可以正確的識別此節點,找到正確的位置直接操作節點,儘可能地進行重用元素,key
的作用主要是為了高效的更新虛擬DOM
。此外,使用index
作為key
是並不推薦的做法,其只能保證Vue
在資料變化時強制更新元件,以避免原地複用帶來的副作用,但不能保證最大限度的元素重用,且使用index
作為key
在資料更新方面和不使用key
的效果基本相同。
示例
首先定義一個Vue
例項,渲染四個列表,分別為簡單列表與複雜列表,以及其分別攜帶key
與不攜帶key
時對比其更新渲染時的速度,本次測試使用的是Chrome 81.0
,每次在Console
執行程式碼時首先會進行重新整理重新載入介面,避免瀏覽器以及Vue
自身優化帶來的影響。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in simpleListWithoutKey" >{{item}}</li>
</ul>
<ul>
<li v-for="item in simpleListWithKey" :key="item" >{{item}}</li>
</ul>
<ul>
<li v-for="item in complexListWithoutKey">
<span v-for="value in item.list" v-if="value > 5">{{value}}</span>
</li>
</ul>
<ul>
<li v-for="item in complexListWithKey" :key="item.id">
<span v-for="value in item.list" :key="value" v-if="value > 5">{{value}}</span>
</li>
</ul>
</div>
</body>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
simpleListWithoutKey: [1, 2, 3, 4, 5, 6],
simpleListWithKey: [1, 2, 3, 4, 5, 6],
complexListWithoutKey:[
{id: 1, list: [1, 2, 3]},
{id: 2, list: [4, 5, 6]},
{id: 3, list: [7, 8, 9]}
],
complexListWithKey:[
{id: 1, list: [1, 2, 3]},
{id: 2, list: [4, 5, 6]},
{id: 3, list: [7, 8, 9]}
],
}
})
</script>
</html>
簡單列表
在簡單列表的情況下,不使用key
可能會比使用key
的情況下在更新時的渲染速度更快,這也就是官方文件中提到的,除非遍歷輸出的DOM
內容非常簡單,或者是刻意依賴預設行為以獲取效能上的提升。在下面的例子中可以看到沒有key
的情況下列表更新時渲染速度會快,當不存在key
的情況下,這個列表直接進行原地複用,原有的節點的位置不變,原地複用元素,將內容更新為5
、6
、7
、8
、9
、10
,並新增了11
與12
兩個節點,而存在key
的情況下,原有的1
、2
、3
、4
節點被刪除,5
、6
節點保留,新增了7
、8
、9
、10
、11
、12
六個節點,由於在DOM
的增刪操作上比較耗時,所以表現為不帶key
的情況下速度更快一些。
// 沒有key的情況下
console.time();
vm.simpleListWithoutKey = [5, 6, 7, 8, 9, 10, 11, 12];
vm.$nextTick(() => console.timeEnd());
// default: 2.193056640625ms
// 存在key的情況下
console.time();
vm.simpleListWithKey = [5, 6, 7, 8, 9, 10, 11, 12];
vm.$nextTick(() => console.timeEnd());
// default: 3.2138671875ms
原地複用可能會帶來一些副作用,文件中提到原地複用這個預設的模式是高效的,但是隻適用於不依賴子元件狀態或臨時DOM
狀態的列表渲染輸出,例如表單輸入值。在不設定key
的情況下,元素中沒有與資料data
繫結的部分,Vue
會預設使用已經渲染的DOM
,而繫結了資料data
的部分會進行跟隨資料渲染,假如操作了元素位置,則元素中未繫結data
的部分會停留在原地,而繫結了data
的部分會跟隨操作進行移動,在下面的例子中首先需要將兩個A
之後的輸入框新增資料資訊,這樣就製作了一個臨時狀態,如果此時點選下移按鈕,那麼不使用key
的組中的輸入框將不會跟隨下移,且B
到了頂端併成為了紅色,而使用key
的組中會將輸入框進行下移,且A
依舊是紅色跟隨下移。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>就地複用</title>
</head>
<body>
<div id="app">
<h3>採用就地複用策略(vuejs預設情況)</h3>
<div v-for='(p, i) in persons'>
<span>{{p.name}}<span>
<input type="text"/>
<button @click='down(i)' v-if='i != persons.length - 1'>下移</button>
</div>
<h3>不採用就地複用策略(設定key)</h3>
<div v-for='(p, i) in persons' :key='p.id'>
<span>{{p.name}}<span>
<input type="text"/>
<button @click='down(i)' v-if='i != persons.length - 1'>下移</button>
</div>
</div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
persons: [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' }
]
},
mounted: function(){
// 此DOM操作將兩個A的顏色設定為紅色 主要是為了演示原地複用
document.querySelectorAll("h3 + div > span:first-child").forEach( v => v.style.color="red");
},
methods: {
down: function(i) {
if (i == this.persons.length - 1) return;
var listClone = this.persons.slice();
var one = listClone[i];
listClone[i] = listClone[i + 1];
listClone[i + 1] = one;
this.persons = listClone;
}
}
});
</script>
</body>
</html>
<!-- 源於 https://www.zhihu.com/question/61078310 @霸都丶傲天 有修改-->
複雜列表
使用key
不僅能夠避免上述的原地複用的副作用,且在一些操作上可能能夠提高渲染的效率,主要體現在重新排序的情況,包括在中間插入和刪除節點的操作,在下面的例子中沒有key
的情況下重新排序會原地複用元素,但是由於v-if
繫結了data
所以會一併進行操作,在這個DOM
操作上比較消耗時間,而使用key
得情況則直接複用元素,v-if
控制的元素在初次渲染就已經決定,在本例中沒有對其進行更新,所以不涉及v-if
的DOM
操作,所以在效率上會高一些。
console.time();
vm.complexListWithoutKey = [
{id: 3, list: [7, 8, 9]},
{id: 2, list: [4, 5, 6]},
{id: 1, list: [1, 2, 3]},
];
vm.$nextTick(() => console.timeEnd());
vm.$nextTick(() => console.timeEnd());
// default: 4.100244140625ms
console.time();
vm.complexListWithKey = [
{id: 3, list: [7, 8, 9]},
{id: 2, list: [4, 5, 6]},
{id: 1, list: [1, 2, 3]},
];
vm.$nextTick(() => console.timeEnd());
// default: 3.016064453125ms
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://cn.vuejs.org/v2/api/#key
https://www.jianshu.com/p/4bdd2690859c
https://www.zhihu.com/question/61078310
https://segmentfault.com/a/1190000012861862
https://www.cnblogs.com/zhumingzhenhao/p/7688336.html
https://blog.csdn.net/hl18730262380/article/details/89306500
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/1
https://cn.vuejs.org/v2/guide/list.html#%E7%BB%B4%E6%8A%A4%E7%8A%B6%E6%80%81