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 屬性裏建立物件的方法來建立 getter 和 setter。

以下會再作詳細解說。

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。注意:需要使用物件來建立 getter 和 setter:

<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))。就是把右邊的值,帶進去 total 的 set 函式裏,也即是 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,只剩下 price 和 discount:

<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,但注意要以物件型別來寫,並在裏面加入 getter 和 setter。
  • 如果畫面沒用到該 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) }}
直播中

尚未有邦友留言

立即登入留言