# 10. 資料觀測與設定

當資料改變的時候,除了更新畫面之外,還要能進行額外的處理。

# 摘要

上圖依序為四組不同型別的資料:

  1. 數字
  2. 物件
  3. 陣列中的數字
  4. 陣列中的物件
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)
    }
  }
})

# 物件

# 物件的變動

物件的變動跟數字相似,被點擊時, objnum 的值會加一。

<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;
    },
}

不過,執行後會發現,雖然第二個方法也能夠觀測到物件中的變動,但是和第一個方法的結果會有些差異。

  1. 印出來的是物件,但是是 "Observer" ,表示是可以被觀測的物件。
  2. 印出來的都是新的值,並不會同時印出新的值和舊的值。

這是因為,物件在記憶體的實體只有一個,這個方式觀測的是整個物件,而不是物件裡的特定項目,所以即使物件裡面的資料有變動,顯示出來的都會是同樣的值。

也因此,如果使用 deep 去觀測物件,newValoldVal 並沒有什麼差別,因為都會是當下的值。

# 陣列中的數字

# 陣列中的數字的變動

在 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 頁面之後,如下圖分別查詢 objListnumberList 和裡面的項目。會發現除了 $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