vue中this.$set()原理
在vue開發中,經常會遇到這樣的問題:當我們給響應式的對象新增屬性時,新增的屬性並不會顯示到頁面中;同樣的對於響應式的數組,增加元素、修改數組長度時,數組的這些變化也不會反映到頁面中(響應式對象和響應式數組是指在vue初始化時期,利用Object.defineProperty()方法對其進行監聽,這樣在修改數據時會及時體現在頁面上)。如下所示:
<div id="app">
{{ obj }}
{{ arr }}
</div>
let vm = new Vue({
el: '#app',
data() {
return {
obj: {
a: '哈哈哈',
},
arr: [1, 3]
}
},
mounted() {
this.changeObj()
this.changeArr()
},
methods: {
changeObj() {
this.obj.b = '新增的屬性b'
console.log(this.obj)
},
changeArr() {
this.arr[4] = 7
this.arr.length = 5
console.log(this.arr)
}
}
})
vm.$data.obj.c = '新增屬性c'
vm.$data.arr[2] = 5
從上面例子中可以看出在頁面加載後,我們通過this.obj.b
新增屬性b時,頁面中並沒有將屬性b顯示出來;通過vm.$data.obj.c
新增屬性c時,頁面中也沒有將屬性c顯示出來;但是通過打印對象obj,可以看出obj上存在屬性b和c,爲什麼呢?
這是由於對象obj及其屬性a在vue初始化時已處理成響應式的,即當我們改變對象obj的值或屬性a的值時,會觸發其在頁面上的更新,但是當頁面加載完成後,新增的屬性b和c就不是響應式的,雖然通過打印this.obj可以看出對象obj上確實增加了屬性b和c,但是由於b和c不是響應式的,所以新增的屬性不會體現在頁面上。
對於數組arr也存在類似的問題,當我們通過索引值添加元素或更改數組長度時,數組本身是發生變化了,但是沒有及時體現在頁面上。
對於上述問題,我們通常使用vue中的this.$set()
來解決,那麼this.$set()
的原理是什麼呢?
在使用this.$set(target, key, value)
時,target爲需要添加屬性的對象,key是要添加的屬性名,value爲屬性key對應的值。
// src/core/observer/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
上面源碼的執行邏輯如下:
1、如果是在開發環境,且target未定義(爲null、undefined)或target爲基礎數據類型(string、boolean、number、symbol)時,拋出告警;
2、如果target爲數組且key爲有效的數組key時,將數組的長度設置爲target.length和key中的最大的那一個,然後調用數組的splice方法(vue中重寫的splice方法)添加元素;
3、如果屬性key存在於target對象中且key不是Object.prototype上的屬性時,表明這是在修改target對象屬性key的值(不管target對象是否是響應式的,只要key存在於target對象中,就執行這一步邏輯),此時就直接將value直接賦值給target[key];
4、判斷target,當target爲vue實例或根數據data對象時,在開發環境下拋錯;
5、當一個數據爲響應式時,vue會給該數據添加一個__ob__
屬性,因此可以通過判斷target對象是否存在__ob__
屬性來判斷target是否是響應式數據,當target是非響應式數據時,我們就按照普通對象添加屬性的方式來處理;當target對象是響應式數據時,我們將target的屬性key也設置爲響應式並手動觸發通知其屬性值的更新;
上面代碼中最重要的就是如下代碼:
defineReactive(ob.value, key, val)
ob.dep.notify()
這兩行是將新增屬性設置爲響應式,並手動觸發通知該屬性值的更新,這就是通過this.$set()
設置之後新增的屬性會變成響應式並及時體現在頁面中的原因。