前言:
在平常的開發中,經常會在vue中用到對象數組,如渲染一個小區的列表,數據結構可能如下所示:
CommunityList: [
{
_id: '',
community_code: '',
community_name: '',
...
},
...
]
當需要實現選擇某幾個小區加入收藏列表的時候,頁面上往往需要增加el-checkbox來勾選,在數據結構上需要增加一個類似relation屬性來標志該小區是否被選擇。
所以在從接口拿到數據的時候需要在數據上增加relation屬性,處理方式可能如下所示:
/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
this.communityList = result.list.map(item => {
item.relation = false;
return item;
})
}
當你碰巧在處理數據之前先將result.list賦值給了另一個實例屬性,如下所示:
/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
this.communityListCache = result.list; // 新增的代碼
this.communityList = result.list.map(item => {
item.relation = false;
return item;
})
}
那么此時el-checkbox的表現可能會不太正常,如下所示:
點擊時狀態不會立馬改變,而是等鼠標移開了才改變

下面兩個圖均是在el-checkbox為灰色狀態下點擊的結果,一個為true,另一個卻是false


分析:
經過打斷點分析發現某些屬性並不是像其它屬性一樣的表現

如這里的relation屬性(el-checkbox綁定的就是該屬性),不像下面的_id屬性所表現的一致,_id屬性所表現的就是正常的被vue監聽的狀態,很明顯relation並沒有被vue監聽,問題就出現在新增的代碼那里。
通過分析vue的源代碼可知,實例屬性被賦值的時候會使用Object.defineProperties方法將其轉化為可被監聽的屬性,如果賦值了一個數組,也會將數組轉化為可被監聽的對象,且會深入內部監聽如下:
截取自vue源代碼中的部分片段:
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value) // 當為數組時,轉化數組
} else {
this.walk(value)
}
}
...
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // 逐級轉化
}
}
}
又因為js中引用類型只是指向實際數據的類似指針的結構,此時this.communityListCache和result.list在內存中實際就是指向同一個地址,當vue轉化this.communityListCache的時候實際就是轉化了result.list
this.communityListCache = result.list; // 新增的代碼
vue會在result.list中新增__ob__這個屬性來標志已經被轉化過了,如下圖所示:

由於relation是在之后才新增的屬性,而在vue轉化的時候,如果對象有__ob__屬性,則表示已經被轉化過了,不會再次去處理,如下:
截取自vue源代碼中的部分片段:
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 判斷是否本來就存在__ob__屬性,如果存在就直接賦值,不做處理
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
這樣后面新增的relation屬性實際就是沒有被vue所監聽的屬性,自然就不會響應變化。
解決辦法:
1、在完全處理好原始數據后再賦值給其它的實例屬性,如下:
/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
// this.communityCache = result.list; // 原始代碼
this.communityList = result.list.map(item => {
item.relation = false;
return item;
});
this.communityCache = result.list; // 新增的代碼
}
2、由於接口返回的數據一般沒有包涵特殊類型,所以在賦值之前進行簡單的深拷貝即可,這樣的話,這兩個變量就不是指向同一個對象,也就不會相互影響:
/** result是從接口獲取的數據 **/
if (result && result.retCode === 0) {
// this.communityCache = result.list; // 原始代碼
this.communityCache = JSON.parse(JSON.stringify(result.list)); // 新增代碼
this.communityList = result.list.map(item => {
item.relation = false;
return item;
});
this.communityCache = result.list; // 新增的代碼
}
注:個人總結,如有不對,還望指正,謝謝!
