vue watch監聽不到物件,探究 watch 原理
最近使用vue watch時,在某些模組監聽不到物件的改變,無法觸發回撥函式。
解決:
使用watch監聽物件時,只能監聽到該物件初始化時已存在的key值。
如下例監聽user物件,在初始化時沒有age屬性,那在mounted中給user.age賦值後不會觸發watch中的回撥:
var app = new Vue({
el: '#app',
data: {
user: {
name: 'zhangsan'
}
},
mounted() {
var _this = this
setTimeout(() => {
_this.user.age = 10
},2000)
},
watch: {
user: {
handler (val) {
console.log('user update')
},
deep:true
}
}
})
若想監聽到這個變化,需要給user初始化的age:
data: {
user: {
name: 'zhangsan',
age: 1
}
},
問題原因及vue watch原理:
這個問題其實是比較低階的錯誤,說明對vue的設計原理和理念還是理解太淺。watch的基本用法這裡不再贅述,下面通過遇到的這個問題,探究一下watch對object型別進行監聽的用法和原理。
通過官方文件我們知道,當監聽的目標用物件進行配置時,共有三個配置屬性:
watch: { a:{
handler: function (val, oldVal) { /* ... */ },
immediate:true,
deep: true
}
}
首先,handler是vue中定義好的屬性名,在用物件配置時,回撥函式只能寫在handler中。如下圖原始碼所示,在creatWatcher時,會驗證傳入的配置項handler是否為一個純物件型別,如果是物件型別,則會取handler.handler作為真正的回撥函式;而下面的程式碼則是隻傳遞方法名或方法實體的處理邏輯。
關於deep屬性,大家都知道vue的繫結原理是建立在Object.defineProperty的set和get方法上的。給某個屬性繫結set和get之後,只有改變該屬性本身時,才會觸發set和get的回撥函式,而改變其內部屬性時不會觸發。所以在vue內部建立watcher時會有一個traverse方法,當配置deep為true時,呼叫traverse遍歷其下每一個子屬性。如下原始碼:
而我們又知道vue只有在元件初始化時,會對data中資料進行set和get的繫結。這也是為什麼在後續的業務邏輯中,給某個物件插入新屬性時不會觸發watch監聽的原因。
immediate屬性為true時,watcher建立後會立刻執行回撥,這點不需要過多的介紹。