計算屬性
基本示例
模板中的內聯表示式非常方便,但它們主要用於簡單的操作。將太多邏輯放入模板會使模板變得臃腫且難以維護。例如,如果我們有一個包含巢狀陣列的物件
js
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
並且我們想要根據作者是否已有書籍顯示不同的訊息
template
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
此時,模板變得有些雜亂。我們需要花點時間才能意識到它根據 author.books
執行計算。更重要的是,如果我們需要多次將此計算包含在模板中,我們可能不想重複自己。
這就是為什麼對於包含響應式資料的複雜邏輯,建議使用 計算屬性。以下是相同的示例,經過重構
vue
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// a computed ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
這裡我們聲明瞭一個計算屬性 publishedBooksMessage
。computed()
函式期望傳入一個 getter 函式,返回值是一個 計算引用。類似於普通的引用,你可以透過 publishedBooksMessage.value
訪問計算結果。計算引用在模板中也會自動展開,所以你可以在模板表示式中直接引用它們而不需要 .value
。
計算屬性會自動跟蹤它的響應式依賴。Vue 知道 publishedBooksMessage
的計算依賴於 author.books
,因此當 author.books
發生變化時,它將更新任何依賴於 publishedBooksMessage
的繫結。
另請參閱:計算屬性型別
計算屬性快取與方法的比較
你可能已經注意到,我們可以透過在表示式中呼叫一個方法來實現相同的結果
template
<p>{{ calculateBooksMessage() }}</p>
js
// in component
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}
與計算屬性不同,我們可以將相同的函式定義為方法。對於最終結果,這兩種方法實際上是完全相同的。然而,區別在於 計算屬性是基於它們的響應式依賴進行快取的。 計算屬性只有在某些響應式依賴發生變化時才會重新評估。這意味著只要 author.books
沒有變化,多次訪問 publishedBooksMessage
將立即返回之前計算的結果,而不需要再次執行 getter 函式。
這也意味著以下計算屬性永遠不會更新,因為 Date.now()
不是一個響應式依賴
js
const now = computed(() => Date.now())
相比之下,方法呼叫將在每次重新渲染時始終執行函式。
為什麼我們需要快取?想象我們有一個昂貴的計算屬性 list
,它需要遍歷一個巨大的陣列並進行大量的計算。然後我們可能有其他依賴於 list
的計算屬性。如果沒有快取,我們可能需要執行 list
的 getter 比必要的次數多得多!在你不想快取的情況下,可以使用方法呼叫。
可寫計算屬性
計算屬性預設是隻讀的。如果你嘗試為計算屬性分配新值,你將收到一個執行時警告。在極少數需要“可寫”計算屬性的情況下,你可以透過提供 getter 和 setter 來建立一個
vue
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// Note: we are using destructuring assignment syntax here.
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
現在當執行 fullName.value = 'John Doe'
時,setter 將被呼叫,並且 firstName
和 lastName
將相應地更新。
獲取上一個值
- 僅支援 3.4+
如果你需要,可以透過訪問 getter 的第一個引數來獲取計算屬性返回的上一個值
vue
<script setup>
import { ref, computed } from 'vue'
const count = ref(2)
// This computed will return the value of count when it's less or equal to 3.
// When count is >=4, the last value that fulfilled our condition will be returned
// instead until count is less or equal to 3
const alwaysSmall = computed((previous) => {
if (count.value <= 3) {
return count.value;
}
return previous;
})
</script>
如果你正在使用可寫計算
vue
<script setup>
import { ref, computed } from 'vue'
const count = ref(2)
const alwaysSmall = computed({
get(previous) {
if (count.value <= 3) {
return count.value;
}
return previous;
},
set(newValue) {
count.value = newValue * 2;
}
})
</script>
最佳實踐
getter 應該無副作用
記住,計算屬性函式應該只執行純計算,並且不產生副作用非常重要。例如,不要修改其他狀態、進行非同步請求或在內聯計算屬性中修改DOM! 將計算屬性視為基於其他值宣告性地描述如何推匯出一個值——其唯一責任應該是計算並返回該值。在指南的後面部分,我們將討論如何使用觀察者來執行對狀態變化的響應。
避免修改計算值
計算屬性返回的值是從衍生狀態推匯出來的。將其視為一個臨時快照——每次源狀態發生變化時,都會建立一個新的快照。修改快照是沒有意義的,因此計算屬性返回的值應被視為只讀的,並且不得修改——相反,應該更新它所依賴的源狀態來觸發新的計算。