iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 21

不只懂 Vue 語法:試說明 computed 的 get 與 set 運作機制?

  • 分享至 

  • xImage
  •  

問題回答

computed 有 getter(取值) 和 setter (寫入值)可使用,但預設只會有 getter 使用,因此 computed 預設是唯讀,不能寫入值。每次建立元件時,都會先跑一次 computed 裏的 getter 來取得此 computed 函式裏的值。之後如果該 computed 函式裏的依賴沒有變化,就不會再跑此 computed 函式,即是不會跑 此函式的getter,並只會一直回傳之前緩存起來的值。

另外,如果要使用 setter,我們可以透過在 computed 屬性裏建立物件的方法來建立 gettersetter

以下會再作詳細解說。

computed 只是唯讀,因為預值只有 getter

預設 computed 只有 getter,意思是我只能透過 computed 函式 來取值。

例如我嘗試在 mounted,觸發 total 函式裏的 setter

<div id="app">
  <p> {{ total }} </p>
</div>
data() {
    return {
      price: 100,
      discount: 0.8
    }
},
computed: {
    total() {
      return this.price * this.discount
    }
},
mounted() {
    this.total = 1000        
}

報錯:

[Vue warn]: Write operation failed: computed property 'total' is readonly.

自行加入 setter

Vue 官網有提到,雖然預設只有 getter 可使用,但我們可以使用物件,並在裏面使用 getter 和 setter。

當你打開 console 查看會看到其實 computed 屬性(以 total 為例)也會有 set

以下例子示範如何使用預設沒有的 setter。注意:需要使用物件來建立 gettersetter

<div id="app">
  <p> 定價: {{ price }} </p>
  <p>折扣:{{ discount }} </p>
  <p>折扣價:{{ total }}</p>
  <button @click="changeComputed">按此隨機產生折扣</button>
</div>
  data() {
    return {
      price: 100,
      discount: 0.5
    }
  },
  computed: {
    // 需使用物件
     total: {
       get() {
         console.log('觸發 getter!')
         return this.price * this.discount
       },
       set(newVal) {
         console.log('觸發 setter!')
         this.discount = newVal
       }
     }
  },
  methods: {
    changeComputed() {
      // 觸發 set,再觸發 get
      this.total = Number(Math.random().toFixed(1))
    }
  }

結果:

程式碼:
https://codepen.io/alysachan/pen/QWgojov

this.total = Number(Math.random().toFixed(1)) 這行雖然看來好像怪怪的?看似是直接把 total 改為隨機產生出來的 discount。但事實上,這裏所做的事是觸發 set 函式,並非修改 total 的值。

重溫一下原生 JavaScript 裏 set(setter) 的用法:

const obj = {
    num: 0,
    get add(){
        return this.num
    },
    set add(newVal){
        this.num += newVal
    }
}
// 使用 get
console.log(obj.add) // 0
// 使用 set
console.log(obj.add = 100) //100

以上 obj.add = 100,就是把 100 帶進去 add 函式裏,並非改掉 add 函式所回傳的值。

因此,回到 Vue 的例子:this.total = Number(Math.random().toFixed(1))。就是把右邊的值,帶進去 totalset 函式裏,也即是 newVal 參數的值。並且修改了 discount 的值。

但為什麼畫面明明顯示total 的值被修改了?因為在 computed 函式裏,所有用到的 data 資料,都會是該函式的依賴。因此當 this.discount 有變化時,就會觸發 total 的更新。

所以整個運作次序就是:

  1. 按按鈕觸發 changeComputed
  2. 觸發 total 裏的 set,修改 discount
  3. 觸發 total 更新值

打開 console 會看到,先觸發 setter,之後才是 getter:

注意,觸發了 set 不代表一定會觸發 get。如果在 set 裏沒有修改到 get 裏的任何一個依賴,get 就不會被觸發,即是不會更新在畫面中 total 的值。例如我只是在 set 裏印出參數:

total: {
    get() {
         return this.price * this.discount
    },
    set(newVal) {
         console.log(newVal)
    }
}

但畫面沒用到 computed,即使依賴有變,也不會執行 getter?

如果畫面中沒有用到 total,只剩下 pricediscount

<div id="app">
  <p> 定價: {{ price }} </p>
  <p>折扣:{{ discount }} </p>
  <!--   <p>折扣價:{{ total }}</p> -->
  <button @click="changeComputed">按此隨機產生折扣</button>
</div>

這情況裏,即使按按鈕,修改了 this.discount,也不會觸發 total 裏的 get。反之,只會一直印出 觸發 setter! 文字。除非你打開了 Vue devtool 檢查工具看一次,之後 Vue 才會執行 getter,並印出 觸發 getter! 文字。

因此可以總結一句,如果在畫面沒用到此 computed 函式,而且沒有在其他程式碼裏有使用過此 computed 函式的值。即代表不會跑該 computed 函式裏的 get,也就是沒有更新此值。

不過這情況不用怎樣擔心,即是如果在畫面上沒用過此值,但你在其他程式碼用到時,還是會寫 this.total。一旦寫了 this.total,就會觸發 total 的 getter,也就一定會取得最新的值。

驗證緩存機制

當然,另一個情況就是當依賴的資料沒變化,就不會執行 getter,並且會一直回傳之前緩存起來的值,也就是 computed 的緩存特性。

例如改一下之前的例子,按按鈕後,把 discount 改為 0.5。結果只會一直印出「觸發 setter!」,因為資料裏的 discount 也是 0.5,結果就沒變化:

methods: {
    changeComputed() {
      // 只會觸發 set
      // 一直都是改為 0.5
      this.total = 0.5
    }
},

總結

  • computed 預設只使用 getter,沒有 setter。因此 computed 函式只會是唯讀。
  • 可以自行在 computed 裏加入 setter,但注意要以物件型別來寫,並在裏面加入 gettersetter
  • 如果畫面沒用到該 computed 函式的值,而在程式碼中也沒有用過此值,Vue 就不會跑該函式裏的 getter,也不知道會回傳什麼值。

參考資料

那些關於 Vue 的小細節 - Computed 中 getter 和 setter 觸發的時間點
Vue 筆記 - Computed 的 get() 與 set()
属性的 getter 和 setter


上一篇
不只懂 Vue 語法:試解釋 computed、watch 與 methods 的差異?
下一篇
不只懂 Vue 語法:為何 v-for 的 key 必須是唯一值?v-for 與 v-if 能否同時使用?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言