本文記錄了我在實現一個簡單的TodoList的過程當中遇到的問題即解決方法。因爲我目前水平較低,仍有未明白的地方,同時文中也可能出現紕漏或者錯誤指出,如果有人能夠看到此文,但願能夠解答個人疑惑或者指出不正之處,謝謝。html
在一個簡單的TodoList應用中,使用v-for
指令綁定待辦事項列表進行渲染。待辦事項列表中每一個對象有一個Boolean
類型的屬性代表該事項是否已經完成,在頁面中經過v-model
將該屬性與一個checkbox
進行雙向綁定,實現經過勾選設置事項是否完成。數組
如今要實現的是將用戶勾選標明已完成的事項放到下面,也就是在點擊checkbox
後調整數組的位置,實現全部的已完成事項都出如今未完成事項的下面。同時,若是從新將已完成的事項取消勾選,其又會上升。函數
本來的作法以下:性能
<ul>
<li v-for="item in todoList">
<input type="checkbox" v-model="item.isFinished" @change="moveEvent(item, $event)">
<span :class="{finished: item.isFinished}">{{ item.name }}</span>
<button @click="removeEvent(item)">刪除</button>
<button @click="topEvent(item)">置頂</button>
</li>
</ul>
複製代碼
moveEvent()
函數以下,作法比較粗暴,先從數組中移除原來的對象,而後找到合適的位置再加進去。ui
moveEvent(item, event){
console.log(event.target.checked)
if(event.target.checked){
this.todoList.splice(this.todoList.indexOf(item), 1);
var index;
for(index = 0; index < this.todoList.length; index++){
if(this.todoList[index].isFinished == true){
break;
}
}
this.todoList.splice(index, 0, item);
}else{
this.todoList.splice(this.todoList.indexOf(item), 1);
var index;
for(index = 0; index < this.todoList.length; index++){
if(this.todoList[index].isFinished == true){
break;
}
}
this.todoList.splice(index, 0, item);
}
}
複製代碼
初始狀態this
當我點擊列表的第一個元素的checkbox
時,出現以下圖的狀況:spa
發現,雖然第一個事項是被移到了下面,而且成功修改成已完成,可是隨後因爲修改了數組產生的新的第一個元素的checkbox
也被勾選上了,可是沒有出現已完成的CSS效果,而且刷新後就能夠正常顯示,說明其實際上並無被修改成已完成。雙向綁定
一樣的,取消勾選會致使相反的狀況,一樣刷新後即會正常顯示。code
能夠判斷咱們的數據並無出錯,而且數組元素的移動也是正常的,可是咱們的checkbox
明明經過v-model
進行了雙向綁定,爲何會出現顯示錯誤的狀況呢?通過定位,最終將問題鎖定在v-for
指令上。先來看看官網對於v-for
指令的介紹:cdn
當 Vue 正在更新使用 v-for 渲染的元素列表時,它默認使用「就地更新」的策略。若是數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是就地更新每一個元素,而且確保它們在每一個索引位置正確渲染。這個相似 Vue 1.x 的 track-by="$index"。
其實這也是一個老生常談的問題了,就是說當咱們改變了數據項順序,Vue並無移動DOM元素,而是從新渲染每一個DOM上的數據,這樣的目的應該是爲了減小DOM操做,提升效率。
那麼回看咱們的問題,大體判斷緣由應該就是出於此:假設咱們把當前索引爲0的元素標記爲已完成,該元素被移到數組下方,而且新位置上渲染結果正確。可是此時因爲從新排序而出如今索引0位置的新元素的checkbox
卻因爲DOM重用的結果,沒有獲得更新,後面的解決方案也驗證了是用於這個緣由形成的。
可是我是進行了v-model
綁定了的,爲什麼仍是會出現這種狀況我尚未徹底明白,在網上查了也沒有類似狀況,如有看到此處的人明白的,還請不吝賜教。
對應的,Vue官網給出瞭解決方法:
爲了給 Vue 一個提示,以便它能跟蹤每一個節點的身份,從而重用和從新排序現有元素,你須要爲每項提供一個惟一 key 屬性。
建議儘量在使用 v-for 時提供 key attribute,除非遍歷輸出的 DOM 內容很是簡單,或者是刻意依賴默認行爲以獲取性能上的提高。
那麼嘗試將v-for
改成:
<li v-for="(item, index) in todoList" :key="index">
複製代碼
結果發現,仍是不行。我認爲緣由應該是這樣的寫法用數組的地址做爲元素的身份,那麼原先被標記爲0(即數組中索引爲0的元素)被成功勾選並下移,而新的數組頭元素此時也被標記爲0,二者身份相同,因此又會錯誤的被勾上!
正確寫法(錯誤寫法)以下,這裏我使用了元素的name
屬性,即事件的名稱:
<li v-for="item in todoList" :key="item.name">
複製代碼
尷尬😅,原本覺得上面的寫法是正確的,可是在寫本文的時候寫着寫着忽然以爲不對,既然索引會出現錯誤,那麼具備相同name
屬性的事件是否是也會出現錯誤?去試驗了一下,果不其然...但仍是記錄下來給本身提個醒。
那麼,通過這麼屢次試驗得出的結論就是須要使用惟一的key
屬性,才能保證萬無一失,例如給每一個事件一個不會重複的id。