16_Vue列表渲染中key的工作原理和虛擬DOM對比算法
key的作用
- 粗略的講,key的作用就是給 節點 設置一個 唯一的標識
- 就像我們人類社會中,每個人的身份證號一樣
- 在大部分對key要求不是很嚴格的場景下,使用index作為key是沒問題的
- 但是我們本章要探討的是,其他情況,可能會出現問題的情況
來看個例子
案例
-
這裡呢,有個ul標籤
-
在內部,li標籤通過v-for渲染 data當中的persons數組
-
<script> var vm = new Vue({ el: '.app', data: { name: 'wavesbright', persons:[ {id:1,name:'張三',age:18}, {id:2,name:"李四",age:19}, {id:3,name:"王五",age:20}, ], }, }); </script>
-
得到了三個節點(3個li)
<div class="app">
<h1>遍曆數組</h1>
<ul>
<li v-for="(item,index) in persons" :key="index">
{{item.name}} - {{item.age}}
</li>
</ul>
</div>
我現在提一個需求
需求
- 我添加一個按鈕,這個按鈕會給我添加一個 老劉 這個對象(老六)
- 這個老劉呢,不能添加在 persons數組的最後面,要在最前面
- 不然看不出問題
click方法只調用一次哈,多了不好分析,這裡使用的是事件修飾符
<div class="app">
<h1>遍曆數組</h1>
<!-- 添加一個人,叫老劉 -->
<button @click.once="addPerson">添加</button>
<ul>
<li v-for="(item,index) in persons" :key="index">
{{item.name}} - {{item.age}}
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '.app',
data: {
name: 'wavesbright',
persons:[
{id:1,name:'張三',age:18},
{id:2,name:"李四",age:19},
{id:3,name:"王五",age:20},
],
},
methods: {
// 添加成員 == 老劉
addPerson(){
// 這是老劉
var laoliu = {id:4,name:'老劉',age:'xx'}
// 在數組頂部添加成員
this.persons.unshift(laoliu)
}
},
});
</script>
測試
感覺沒什麼問題呀,為什麼會講這個呢
警告,錯誤沒有,頁面顯示正常,也沒有報錯
有問題嗎,有,這裏面有個很嚴重的問題
增加需求
- 我現在再來一個需求
- 給每個 li標籤當中,添加一個input框
再來測試下
沒問題啊,咋地?來,我給你演示下
問題出現
- 我在添加老劉之前,我先給張三李四王五的input框框中,輸入一些文字
- 然後添加老劉,我們來看看這次是什麼樣子的
測試
- 我們希望看到的是什麼? == 老劉出現的時候,是一個空白的input框
- 但現在問題是什麼?下方的三個兄弟的信息 分別錯位了 為什麼會這樣
- 那看看 現在我將 :key換成 item.id會怎麼樣
再測試
哦~那我們現在可以總結一個情況
- 使用index會出現這個問題
- 但是使用自帶的id就沒這個問題,這是我們現在所遇到的情況
key的工作原理和對比算法
這裡使用流程圖講解
分析index作為key
初始化流程
1、一切的一切都是你寫了這段代碼
2、vue是不是會拿着你的數據生成 虛擬DOM?(並不是一開始就把數據給你變為頁面DOM的,是有流程的)
3、真實DOM上是沒有這個key的,虛擬DOM上必須要有(沒有,vue不能高效工作)
4、上圖這個是已經生成真實DOM了,我們現在還處於虛擬DOM生成的過程中(假設現在頁面還沒有生成這三個li標籤)。現在,在內存當中是不是有這三個虛擬DOM了(3個li標籤)
5、將虛擬DOM轉換為真實DOM
6、請問,用戶是在哪裡操作的數據?虛擬DOM還是真實DOM,用戶輸入的數據,殘留在誰身上了?虛擬還是真實DOM?(用戶操作的全是真實DOM)
這個時候初始化流程就結束了
數據更新,老劉出現
1、新的數據出現了,老劉出現了,老劉是排在所有人的前面
2、隨後,會根據新的數據,生成新虛擬DOM(因為數據發生改變了)
這個時候,老劉的key就是0了,因為你使用index作為索引,老劉排在最前面(因為我們插在最前面)
重點來了:,在目前的整個流程當中,生成了兩份虛擬DOM,vue不會根據上面這個修改後的虛擬DOM進行真實DOM的創建,而是會將兩個虛擬DOM進行一個對比算法,這就是該算法的由來
虛擬DOM的對比算法
對比的時候,依賴着這個key,怎麼對比的呢?請讓我用文字來為你形容
- 首先,它來到這個新的虛擬DOM當中,按照順序,先取出第一個(老劉)
- 然後它就問,你的key是多少,老劉回答 0
- 接下來,它來到舊的虛擬DOM中,尋找和 老劉擁有相同key值的人 誰呢? 張三吖
- 找打了,咋的呢?
- 它會對比兩個節點的內容
- 怎麼對比的呢?
- 拿出 右側的 老劉-30 與 左側的 張三-18 ,一對比,誒,兩邊不相等
- 不一樣了,怎麼著?我們可以得出一點,key值為0的兩個虛擬DOM內容不相同
- 接下來,因為二者內部都存在 input,開始對比input的內容
- 這裡先暫停下,我來問個問題?請問,對比二者的input的時候,它們input的內容是否相等?
- 答案是相等的,因為這倆個都是虛擬DOM
- 用戶輸入數據的時候,數據是殘留在真實DOM中的
- 人家是在內存當中對比的虛擬DOM,
- 最終input這裡對比的結果就是相等的
- 剛剛對比不一樣(老劉-30與張三-18)的怎麼辦,一樣的(input)怎麼辦呢?
- 一樣的結果就是 復用
- 什麼是復用呢?
- 你看,我老劉這裡有個input,你張三這裡也有個input
- 我老劉的key是0,你張三的key也是0
- 那麼作為唯一標識,咱倆是來自同一個體系的
- 要不你看這樣吧,你這個張三的虛擬DOM一定轉換過真實DOM(都用對比算法了肯定轉換過)
- 你看昂,張三的input轉換過真實DOM,那麼我老劉的input與張三的input是一樣的(虛擬DOM中)
- 那我老劉就沒必要把 li 當中 的 input轉換為 真實DOM了
- 我直接拿 張三的 input(真實) 復用
- 那我老劉就沒必要把 li 當中 的 input轉換為 真實DOM了
- 也就出現了下面的情況
- 虛擬DOM進行了對比
- 對比的結果 決定了 還是使用之前 張三的 真實input (不需要將老劉的虛擬input轉換為真實input)
- 然鵝,張三的真是input當中,還殘留着用戶的輸入
- 那麼,搬用過來的時候,把殘留輸入一起帶過來了
- 錯在哪裡
- 使用index作為key,導致了 復用 的發生,順序亂了
- 由於這個細節錯誤的出現,導致了錯位的生成
- 以此類推,到王五的時候,key = 3在舊的虛擬DOM當中不存在,那麼只有自己生成了
我們出現這個錯誤的原因是執行了這個奇葩的需求,但是通過這個需求,我們能對key進行更加深入的理解
為什麼說效率低
- 我們來看圖,圖裡解釋的很清楚了
- 二者的input是相等的,在虛擬DOM當中
- 但是二者的 插值語法這個位置
- 是不相等的,不相等,那麼就沒有辦法採用 復用行為
- 沒有辦法採用復用行為,那麼 插值語法這裡的真實DOM就需要自己生成
- 也就是這一塊,是新的虛擬DOM轉換為真實DOM自己生成的
分析item.id作為key
通過上面的分析,這裡思路就很清晰了
- 還是一樣的,因為這些初始化數據,我們在內存當中生成了虛擬DOM
- 虛擬DOM轉換為真實DOM
- 用戶在真實DOM進行操作,殘留了輸入
- 接下來我們添加了一個成員,老劉
- 那麼虛擬DOM被更新了
- 更新了,那麼就會和舊的虛擬DOM進行對比
- 老劉的key是004,舊的DOM當中是沒有key為004的元素的,那麼老劉就需要自己生成
- 張三的key是001,舊的虛擬DOM當中有嗎?有的,那麼input一樣嗎,一樣的,那就復用唄
- 同理李四和王五也是
- 老劉的key是004,舊的DOM當中是沒有key為004的元素的,那麼老劉就需要自己生成
- 所以使用id作為key是不會出現剛剛那個問題的
- 效率高嗎,高,因為服用了,錯位了嗎,沒有
不寫key
- 如果你不寫key,那麼vue在遍歷的時候會默認將索引值index作為key
- 既然index作為key了,這個DOM身上是有key的,那麼自然不會報錯
- 既然index作為key了,遇見剛剛那個問題自然會出現問題
面試題
react、vue中的key有什麼作用?
分為如下幾步回答