用 :key 管理可復用元素
- 2019 年 11 月 7 日
- 筆記
input
中的 key
我們先來看一個切換登錄方式的例子:
<div v-if="isUser"> <label>Login with account</label> <input type="text" placeholder="Enter your account"> </div> <div v-else> <label>Login with email</label> <input type="text" placeholder="Enter your email"> </div> <button @click="isUser=!isUser">click to toggle</button>


我們會發現,在點擊按鈕切換登錄方式後,輸入框中已有的內容沒有被清除,這是為什麼呢?
引用官方文檔的原話:
Vue 會儘可能高效地渲染元素,通常會復用已有元素而不是從頭開始渲染。
這裡的 input
實際上復用了切換之前的 input
。而類似 <input>
,<select>
,<textarea>
這樣的表單元素都有一個 internal state
保存著元素的值,在元素復用時,這個值是會得到保留的。
如果我們希望切換的時候不保留這個值呢?我們可以給兩個 input
添加不同的 key
。因為 Vue 是將 key
作為唯一標識從而來識別復用的元素的,如果兩個元素的 key
不同,那麼就相當於告訴 Vue 「這兩個元素是完全獨立的,你不能用其中一個來複用另一個」。
接著再來看一個利用 v-for
生成 input
的例子。 假如我們的程式碼為:
<div id="app"> <div v-for="(item,index) in array"> {{item}}: <input type="text"> </div> </div>
const app = new Vue({ el:'#app', data:{ array:["A","B","C","D","E"] } })
之後生成的 input
中我們填入字元串作為 internal state
。如圖:

在沒有使用 key
的情況下,我們通過 app.array.splice(2,0,"F")
在 BC 之間插入 F,發現:

和之前一樣,因為 Vue 採用的是 就地復用
策略,這意味著 ABCDE 在原地不動的情況下被複用了,CDE 都被重新渲染了一次,但先前的 internal state
仍然保留著。
出於性能考慮,有沒有辦法可以只移動個別元素,單獨渲染要插入的那個新元素呢?有了前面的經驗,我們會想到給每個 input
一個 key
值。
首先我們嘗試將 index
作為 key
,之後進行插入操作,發現:

問題依然存在。這是因為,我們將 index
作為復用的判斷依據,相當於告訴 Vue:「只要這兩個東西的 index 一樣,就進行復用」。插入之前 C 的 index 是 2,插入之後 F 的 index 也是 2,於是 F 復用了 C,同理,DE 也被複用了,並因此重新渲染了一次。
index
是會隨著插入刪除改變的值,所以它實際上並不適合作為 key
。於是我們想:在進行插入或者刪除操作的時候,有沒有一種值始終不會改變呢?有的,我們可以給每個元素一個單獨的 id。但更簡單的方法是直接使用 item
,即元素本身的值,畢竟這個值對每個元素來說也是獨一無二的。
我們將 item
作為 key
,之後進行插入操作,發現:

這回正常了。可以很明顯地看到,每個元素都復用了先前的對應元素,這是因為此時 item
(即元素值)才是復用的判斷依據,相當於告訴 Vue:「只要這兩個東西的元素值一樣,就進行復用」。例如對於 C 來說,它只會復用與自己的值一樣的元素,顯然這個元素就是 C 本身。同理,D 復用 D,E 復用 E,CDE 都不需要重新渲染了,只需要後移以方便 F 插入,這時候的性能顯然要好很多。
Virtual DOM 的 Diff
演算法
下面大致從虛擬DOM的Diff演算法實現的角度去解釋一下。
vue 和 react的虛擬 DOM 的 Diff
演算法大致相同,其核心是基於兩個簡單的假設:
- 兩個相同的組件產生類似的DOM結構,不同的組件產生不同的DOM結構。
- 同一層級的一組節點,他們可以通過唯一的id進行區分。基於以上這兩點假設,使得虛擬DOM的Diff演算法的複雜度從O(n^3)降到了O(n)。
引用 React』s diff algorithm 中的例子:

當某一層有很多相同的節點時,也就是列表節點時,Diff 演算法的更新過程默認情況下也是遵循以上原則。 比如一下這個情況:

我們希望可以在 B 和 C 之間加一個 F,Diff 演算法默認執行起來是這樣的:

即把 C 更新成 F,D 更新成 C,E 更新成 D,最後再插入 E,這樣顯然很沒有效率。 所以我們需要使用 key 來給每個節點做一個唯一標識,Diff 演算法就可以正確的識別此節點,找到正確的位置區插入新的節點。

所以 key 的作用主要是為了高效的更新虛擬 DOM。
參考: https://stackoverflow.com/questions/44077320/what-is-the-use-of-track-by-or-key-in-v-for-in-vue-js https://juejin.im/post/5aae19aa6fb9a028d4445d1a#comment