最近在用vue的時(shí)候遇到了一個(gè)問題粥诫,于是我寫了一個(gè)簡單的demo進(jìn)行了測試油航,直接上圖:
這是一個(gè)列表,列表中有三項(xiàng)怀浆,每項(xiàng)都是子組件谊囚,我的預(yù)期效果:點(diǎn)擊第一項(xiàng)的“刪除”按鈕怕享,就刪除第一項(xiàng),點(diǎn)擊第二項(xiàng)的“刪除”按鈕秒啦,就刪除第二項(xiàng),以此類推搀玖。
然而......
我點(diǎn)擊了第二項(xiàng)(內(nèi)容為“22”)的刪除按鈕余境,希望能把該項(xiàng)刪掉,結(jié)果發(fā)現(xiàn)頁面上把最后一項(xiàng)刪掉了灌诅,但是vue-devtools里的數(shù)據(jù)又確實(shí)按照預(yù)期改變了芳来。
What?猜拾?這是什么操作即舌??挎袜?
在這個(gè)demo中顽聂,我使用了子組件作為列表的每項(xiàng),使用vuex做統(tǒng)一的狀態(tài)管理盯仪,以下是我的代碼:
App.vue:
<template>
<div id="app">
<ul>
<li v-for="(value,key) in currentArray" class="list-item">
<EditableField :content="value" @change="changeContent"></EditableField>
<div class="action">
<a href="javascript:;" @click="test(key)">刪除</a>
</div>
</li>
</ul>
</div>
</template>
<script>
import store from './store/index'
import EditableField from './components/EditableField'
export default {
name: 'App',
components: {
EditableField
},
store,
data(){
return {
currentArray:this.$store.state.data.list
}
},
methods:{
changeContent(){
console.log('內(nèi)容改變了')
},
test(index){
console.log('刪除:'+index)
this.$store.commit('deleteItem',{name:'list',index:index})
}
}
}
</script>
./store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store=new Vuex.Store({
state:{
data:{
list:[
'11',
'22',
'33'
]
}
},
mutations:{
deleteItem(state,payload){
let {name,index}=payload
state['data'][name].splice(index,1)
}
}
})
export default store
./components/EditableField.vue
<template>
<p contenteditable="true" @input="changeText"></p>
</template>
<script>
export default{
name:'EditableField',
props:['content'],
mounted:function () {
this.$el.innerHTML=this.content
},
methods:{
changeText(e){
this.$emit('change',e.target.innerHTML)
}
}
}
</script>
<style scoped>
p{
word-break: break-all;
}
</style>
我剛開始想紊搪,是不是由于數(shù)組更新檢測的問題,導(dǎo)致vue沒有檢測到數(shù)組的變化全景,因此沒有同步到頁面上耀石?但是翻了vue關(guān)于數(shù)組更新檢測的文檔后,我發(fā)現(xiàn)我的寫法并沒有錯(cuò)爸黄。
正當(dāng)我冥思苦想之際滞伟,我偶然發(fā)現(xiàn),如果我不用子組件炕贵,結(jié)果就是對的:
App.vue:
<template>
<div id="app">
<ul>
<li v-for="(value,key) in currentArray" class="list-item">
<!---------------------------- 看這里0鹉巍!称开!------------------------------>
<p v-html="value" contenteditable="true" @input="changeContent"></p>
<!--<EditableField :content="value" @change="changeContent"></EditableField>-->
<!----------------------------------------------------------------------->
<div class="action">
<a href="javascript:;" @click="test(key)">刪除</a>
</div>
</li>
</ul>
</div>
</template>
我現(xiàn)在刪除第二項(xiàng)(內(nèi)容為“22”)鉴裹,頁面上和vue-devtools里的數(shù)據(jù)都符合預(yù)期。
所以問題應(yīng)該是出在這個(gè)子組件的使用上钥弯。
翻看文檔后径荔,我發(fā)現(xiàn)了 “key” 這個(gè)特殊屬性(文檔里關(guān)于key的兩處介紹:教程、API文檔)脆霎,由此我們知道:
- 用v-for正在更新已渲染過的元素列表時(shí)总处,它默認(rèn)采用“就地復(fù)用”策略,該模式是高效的睛蛛,但是只適用于不依賴子組件或者臨時(shí)DOM狀態(tài)(例如:表單輸入值)的列表渲染輸出鹦马。
- key的特殊屬性主要用在Vue的虛擬DOM算法胧谈,在新舊nodes對比時(shí)辨識VNodes。如果不使用key荸频,Vue會使用一種最大限度減少動態(tài)元素并且盡可能的嘗試修復(fù)/再利用相同類型元素的算法菱肖。使用key,它會基于key的變化重新排列列表元素順序旭从,并且會移除key不存在的元素稳强。
由于我的列表的每一項(xiàng)都是子組件,所以顯然我的列表依賴了子組件和悦,所以應(yīng)該在每一項(xiàng)加“key”退疫,使上面提到的“就地復(fù)用”策略失效。于是現(xiàn)在只需要給數(shù)組中的每項(xiàng)添加一個(gè)唯一標(biāo)識key就可以了鸽素。
App.vue:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store=new Vuex.Store({
state:{
data:{
//-------------修改list---------------
list:[
{
key:'1',
value:'11'
},
{
key:'2',
value:'22'
},
{
key:'3',
value:'33'
}
]
//---------------------------------------
}
},
getters:{
todos:state=>{
return state.data.list
}
},
mutations:{
deleteItem(state,payload){
let {name,index}=payload
state.data.list.splice(index,1)
}
}
})
export default store
相應(yīng)修改App.vue(在每項(xiàng)li上添加key):
<template>
<div id="app">
<ul>
<li v-for="(value,key) in currentArray" class="list-item" :key="value.key">
<EditableField :content="value.value" @change="changeContent"></EditableField>
<div class="action">
<a href="javascript:;" @click="test(key)">刪除</a>
</div>
</li>
</ul>
</div>
</template>
結(jié)果正確褒繁。
至此,這個(gè)問題就解決了馍忽。
但是我由此有了另一個(gè)疑問:數(shù)據(jù)要怎樣更新呢棒坏?既然子組件在有key的時(shí)候才更新,每條數(shù)據(jù)的key以數(shù)據(jù)的key作為唯一標(biāo)識遭笋,如果數(shù)據(jù)的key不變俊抵,但是value改變了,這樣子組件的數(shù)據(jù)依然不會更新坐梯。
帶著這個(gè)疑問徽诲,我又進(jìn)行了測試:
index.js:
const store=new Vuex.Store({
state:{
data:{
list:[
{
key:'1',
value:'11'
},
{
key:'2',
value:'22'
},
{
key:'3',
value:'33'
}
]
}
},
mutations:{
editItem(state,payload){
let {index}=payload
let data={
key:'2',
value:'22aaaaaa'
}
//-----------修改key值為2的數(shù)據(jù)的value------------
let tmp=state.data.list.find((i)=>i.key===data.key)
Object.assign(tmp,data)
}
}
})
結(jié)果如下:
這個(gè)結(jié)果是符合我們預(yù)期的:由于修改的數(shù)據(jù)的key值沒有改變,該key值對應(yīng)的數(shù)據(jù)也不會更新視圖吵血。
所以我們只需要在改變數(shù)據(jù)value的同時(shí)谎替,改變它的key值:
index.js:
const store=new Vuex.Store({
state:{
data:{
list:[
{
key:'1',
value:'11'
},
{
key:'2',
value:'22'
},
{
key:'3',
value:'33'
}
]
}
},
mutations:{
editItem(state,payload){
let {index}=payload
let data={
key:'2',
value:'22aaaaaa'
}
//-----------修改key值為2的數(shù)據(jù)的value------------
let tmp=state.data.list.find((i)=>i.key===data.key)
data.key='4' //改變key值
Object.assign(tmp,data);
}
}
})
結(jié)果:
注意幾點(diǎn):
- 在實(shí)際項(xiàng)目中,每條數(shù)據(jù)肯定有它的id或者code作為唯一標(biāo)識蹋辅,但是該標(biāo)識不可直接設(shè)置為key的值(因?yàn)閿?shù)據(jù)的id或者code不會改變)钱贯;
- key要保證唯一性,可以設(shè)置它為自增的數(shù)字侦另,或者結(jié)合日期時(shí)間設(shè)置隨機(jī)字符串秩命。
- 如果在列表循環(huán)的時(shí)候不使用子組件,則不會出現(xiàn)上述問題褒傅。
以上就是本篇博客的全部內(nèi)容弃锐。每一次的踩坑都是進(jìn)步,今天多踩一個(gè)坑殿托,明天就能少踩一個(gè)坑霹菊,生命不息,填坑不止支竹。
由于個(gè)人水平有限旋廷,博客錯(cuò)誤之處鸠按,煩請指正!