Vue深入響應式原理之物件 - 為什麼我的修改沒有生效?
如有錯誤望請指出
在開發過程中可能會遇到修改了值,檢視未發生更新的情況。
實際上在vue的官方文件中也有描述,見 深入響應式原理
這裡會以實際案例講述
問題重現
下面是一個迴圈顯示物件的vuejs程式碼(建議有條件的可以直接執行檢視效果)
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<div v-for="(v,k) in obj">{{k}} => {{v}}</div>
<button @click="setA">賦值A</button>
<button @click="setB">賦值b</button>
<button @click=" print">列印</button>
</div>
</body>
<script>
new Vue({
el: '#root',
data() {
return {
obj: {
a: 1
}
}
},
methods: {
setA() {
this .obj.a = 'new A';
},
setB() {
this.obj.b = 2;
},
print() {
console.log(this.obj)
}
}
});
</script>
</html>
通過瀏覽器開啟後初始狀態如下圖:
點選賦值A
後,頁面發生了預期的變化,結果如下:
點選 賦值b
後,此時發現並沒有發生預期的變化,html並沒有渲染出b => 2
這樣的內容。
但實際物件確實新增了一個值為2
的b
屬性,點選列印
驗證
如何解決?
方法一:提前定義
咱們可以很明顯的發現差異,a
是原來就定義在obj
中的,而b
不是。
因此第一個方法就是定義一個空值b
。如下:
...
...
data() {
return {
obj: {
a: 1,
b: null
}
}
}
...
...
方法二:使用 $set
方法一
雖然有效,但有時確實存在無法預先知道要新增的屬性名或者說懶得(?)。
出現問題的本質是vue不知道新增了一個b
屬性,vue本身提供了一個$set方法用於解決這個問題,將setB
中賦值方法修改。
程式碼如下:
setB() {
// this.obj.b = 2;
this.$set(this.obj, 'b', 2);
},
此時再點選賦值b
就可以看到html發生了符合預期的變化。
為什麼?
要實現在值發生變化時頁面也同時發生改變,關鍵是在於發現值的變化。
Vue利用了Object.defineProperty
(這裡有更詳細的描述)。簡單的說,使用這個方法可以給物件的某一個屬性加上getter/setter
方法,之後在讀取這個值時會執行getter
方法,在設定時會執行setter
方法。以此就可以監聽到物件屬性的存取操作,繼而也就能實現html的更新。
因此,當在data
中定義完值並返回後,vue會對其中物件(陣列的情況暫不考慮)
的所有屬性,進行遞迴式的Object.defineProperty
。
這也是為什麼,需要提前定義。因為這個方法並不能監聽到物件屬性的新增
與刪除
。
vue作者因此提供了$set
與$delete
這樣的顯式操作。以支援響應式。
另外對於陣列
對於陣列,可以知道能夠對陣列自身產生變化的方法只有七個。
分別是:push
,pop
,shift
,unshift
,splice
,sort
,reverse
因此vue中對這些方法重新進行了定義,能夠在呼叫這些方法時發現數組的變化。
所以!當以陣列下標的方式修改陣列時,vue也是發現不了的,這個時候還是得使用$set
來解決。
其他:Proxy
在vue3中Object.defineProperty
方式被放棄,轉而使用Proxy
實現對資料的觀察。Proxy
從能力上是優於Object.defineProperty
的,更詳細內容的,先自己查查吧。。