➤ computed 是計算屬性:值是根據其他資料衍生出來的結果
Vue 的 computed 計算屬性會自動追蹤響應式依賴,它就像是一個加上「感應器」的 getter,Vue 會追蹤裡面用到的 reactive 變數。當使用 computed 時,一旦那些依賴的變數變動,這個計算結果就會重新運算。
和 method 不同,computed 會快取結果:只要依賴沒變,就會直接回傳上次的值,不會重新執行函式,這在需要計算成本的情境(例如複雜排序、過濾等)尤其重要。
這種「根據其他資料計算出衍生資料,且具備快取與響應能力」的設計,就是 computed 的本質。
把 computed 想像成「智慧型小白板」,它會寫下根據資料算出的東西,只要來源資料沒動,它就不會擦掉重寫,節省計算資源。
- 具備「快取」的 effect
- 只要依賴的值不變時就不會重新計算
- 實作上會建立 lazy 的 effect,並追蹤依賴是否髒了(dirty)
- 支援 setter(雙向綁定)
<script setup>
import { ref, computed } from 'vue'
const height = ref(180)
const weight = ref(75)
const bmi = computed(() => {
const h = height.value / 100
return (weight.value / (h * h)).toFixed(1)
})
const suggestion = computed(() => {
const value = parseFloat(bmi.value)
if (value < 18.5) return '體重過輕'
if (value < 24) return '正常'
if (value < 27) return '過重'
return '肥胖'
})
const suggestionClass = computed(() => {
switch (suggestion.value) {
case '體重過輕':
return 'text-blue-500'
case '正常':
return 'text-green-600'
case '過重':
return 'text-yellow-500'
case '肥胖':
return 'text-red-600'
default:
return ''
}
})
</script>
<template>
<div class="p-4">
<label>
身高(cm):
<input type="number" v-model.number="height" />
</label>
<br />
<label>
體重(kg):
<input type="number" v-model.number="weight" />
</label>
<br />
<p>BMI:<strong>{{ bmi }}</strong></p>
<p>建議:<span :class="suggestionClass">{{ suggestion }}</span></p>
</div>
</template>
計算屬性的 getter 應只做計算而沒有任何其他的副作用(不要改變其他狀態、在 getter 中做非同步請求或者更改 DOM),getter 的職責應該僅為計算和返回該值。而 computed 的返回值應該被視為只讀的,並且永遠不應該被更改——應該更新它所依賴的源狀態以觸發新的計算。
像 reverse()
和 sort()
這類方法是「會改變原陣列本身」的,所以在 computed 裡面使用時,如果不小心直接操作原始資料,就等於破壞了響應式狀態的「單向資料流」。
<script setup>
import { computed } from 'vue'
const sortedNumbers = computed(() => numbers.value.sort())
const reversed = computed(() => numbers.value.reverse())
</script>
<script setup>
import { computed } from 'vue'
// [...] 使用展開運算子會創造一個新的陣列,原始的 numbers.value 完全不會被動到
const sortedNumbers = computed(() => [...numbers.value].sort())
const reversed = computed(() => [...numbers.value].reverse())
</script>