# 10. 資料觀測與設定
當資料改變的時候,除了更新畫面之外,還要能進行額外的處理。
# 摘要
上圖依序為四組不同型別的資料:
- 數字
- 物件
- 陣列中的數字
- 陣列中的物件
let data = {
number: 0,
obj: {num: 0},
numberList: [0, 0, 0],
objList: [{num:0}, {num:0}, {num:0}]
}
每當滑鼠點擊白色圓圈時,該圓圈裡的數字會加一。
觀察不同型別的資料調整時,畫面是否更新。以及如何對這些資料進行觀測。
# 數字
# 數字的變動
首先是數字的變動。由於程式碼較短,所以直接在 HTML 的標籤寫上 @click="number++"
:
<div class="description">
<div @click="number++">{{ number }}</div>
</div>
# 數字的觀測
再來,是數字的觀測。在建構的 vm
中,使用 watch
去監看 data
中的 number
是否改變,參數使用 (newVal, oldVal)
,依序代表新舊值。如果 number
有變化,就把變化前後的值印出來。
let vm = new Vue({
el: '#app',
data: data,
watch: {
number(newVal, oldVal){
console.log('number: '+ newVal, oldVal)
}
}
})
# 物件
# 物件的變動
物件的變動跟數字相似,被點擊時, obj
中 num
的值會加一。
<div class="description">
<div @click="obj.num++">{{ obj.num }}</div>
</div>
# 物件的觀測
物件的觀測和數字的觀測不同,因為變動的是物件裡面的項目 (obj.num
),而不是物件本身 (obj
),如果照觀測數字的方式去觀測物件,是不會印出任何東西的。
解決的方式有兩個,一個是清楚寫出要觀測的是物件中的哪一項,例如 ['obj.num']
。
watch: {
number(newVal, oldVal){
console.log('number: '+ newVal, oldVal)
},
// 方法一:直接觀測物件裡面的某個項目。
['obj.num'](newVal,oldVal){
console.log('obj: ', newVal, oldVal)
},
}
另一個方式,是對物件本身開啟 deep
,觀測這個物件裡的每個一項,只要物件裡面有任意項目的值變動,就執行 handler
。
watch: {
number(newVal, oldVal){
console.log('number: '+ newVal, oldVal)
},
// 方法二:使用 deep 觀測整個物件本身,有異動就執行 handler。
obj: {
handler(newVal, oldVal){
console.log('obj: ' , newVal, oldVal)
},
deep: true;
},
}
不過,執行後會發現,雖然第二個方法也能夠觀測到物件中的變動,但是和第一個方法的結果會有些差異。
- 印出來的是物件,但是是 "Observer" ,表示是可以被觀測的物件。
- 印出來的都是新的值,並不會同時印出新的值和舊的值。
這是因為,物件在記憶體的實體只有一個,這個方式觀測的是整個物件,而不是物件裡的特定項目,所以即使物件裡面的資料有變動,顯示出來的都會是同樣的值。
也因此,如果使用 deep
去觀測物件,newVal
和 oldVal
並沒有什麼差別,因為都會是當下的值。
# 陣列中的數字
# 陣列中的數字的變動
在 HTML 綁定 numberListHandler
,當滑鼠點擊項目時,對應的 index
數字會加一。
<div class="description">
<div v-for="(item,index) in numberList" @click="numberListHandler(index)">{{ item }}</div>
</div>
如果照之前在 JavaScript 的作法,在 Vue 的 methods
使用 this.numberList[index]++
來改資料,會發現點擊這個陣列裡面的數字後,畫面跟資料都不會更新,直到去點了別的東西,更新了畫面的同時,這些陣列中的數字才會隨之一次更新到畫面上。
methods: {
numberListHandler(index){
this.numberList[index]++
}
}
我們可以用 Vue.js devtools 來找原因。在開發者工具切換到 Vue 頁面,點擊 "<Root>
" 之後會出現 $vm0
,$vm0
可以在 Console 頁面用來看 Vue 裡面的東西。
切換到 Console 頁面之後,如下圖分別查詢 objList
、numberList
和裡面的項目。會發現除了 $vm0.numberList[0]
之外,都有 Observer 。也就是說,只要資料被改動了,就會被通知。
至於 $vm0.numberList[0]
,則是單純的數字。資料改了,但沒有通知,所以畫面不會更新。直到接下來有通知出現的時候,才會更新畫面。
解決方式是不要直接改值,而是使用 $set(要改變的東西, 要改變第幾項, 要改成什麼樣子)
間接設定新的值。
methods: {
numberListHandler(index){
// this.numberList[index]++
this.$set(this.numberList, index, this.numberList[index]+1)
}
}
# 陣列中的數字的觀測
陣列中的數字的觀測方式,跟單純觀測數字時相同,只要在函式中使用新舊參數即可。
watch: {
number(newVal, oldVal){
console.log('number: '+ newVal, oldVal)
},
['obj.num'](newVal,oldVal){
console.log('obj: ', newVal, oldVal)
},
numberList(newVal,oldVal){
console.log('numberList: ' , newVal, oldVal)
}
}
觀測陣列中的數字時,不需要用到 deep
。這是因為使用 $set()
的時候,就是用 numberList
本身去打通知,具有 Observer 的作用,所以不需要使用 deep
進行觀測。
不過,因為陣列在記憶體中和物件一樣只有一個,所以每次印出來的也會是同樣的值。
# 陣列中的物件
# 陣列中的物件的變動
在 HTML 綁定 objListHandler
點擊事件。
<div class="description">
<div v-for="(item,index) in objList" @click="objListHandler(index)">{{ item.num }}</div>
</div>
methods: {
numberListHandler(index){
this.$set(this.numberList, index, this.numberList[index]+1)
},
objListHandler(index){
this.objList[index].num++
}
},
# 陣列中的物件的觀測
因為要觀測的是陣列中的所有物件,所以使用 handler
搭配 deep
較深度地觀測 objList
的變化。
watch: {
number(newVal, oldVal){
console.log('number: '+ newVal, oldVal)
},
['obj.num'](newVal,oldVal){
console.log('obj: ', newVal, oldVal)
},
numberList(newVal,oldVal){
console.log('numberList: ' , newVal, oldVal)
},
objList: {
handler(newVal,oldVal){
console.log('objList: ', newVal, oldVal)
},
deep: true
}
}
# 小結
- 使用
watch
可以觀察資料的改變。- 非物件資料:直接觀察。
- 物件資料:
- 針對物件裡的某一項參數去觀察。例如
['obj.num']
- 對整個物件去進行觀察。須設定
deep: true
,且因為物件傳址的特性,所以無法拿到舊的值。
- 針對物件裡的某一項參數去觀察。例如
- 陣列資料:
- 陣列中的數字不是 observer,所以需要使用
$set()
間接改資料,畫面才會跟著動。 - 使用
$set()
改值會打通知,所以直接觀察即可,不用像物件一樣要用deep
。
- 陣列中的數字不是 observer,所以需要使用