跳轉到內容

計算屬性

基本示例

模板中的內聯表示式非常方便,但它們主要用於簡單的操作。將太多邏輯放入模板會使模板變得臃腫且難以維護。例如,如果我們有一個包含巢狀陣列的物件

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
}
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 執行計算。更重要的是,如果我們需要多次將此計算包含在模板中,我們可能不想重複自己。

這就是為什麼對於包含響應式資料的複雜邏輯,建議使用 計算屬性。以下是相同的示例,經過重構

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    // a computed getter
    publishedBooksMessage() {
      // `this` points to the component instance
      return this.author.books.length > 0 ? 'Yes' : 'No'
    }
  }
}
template
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>

在沙箱中嘗試

在這裡,我們聲明瞭一個計算屬性 publishedBooksMessage

嘗試更改應用程式 databooks 陣列的值,您將看到 publishedBooksMessage 如何相應地變化。

您可以在模板中將計算屬性與資料繫結,就像繫結正常屬性一樣。Vue知道 this.publishedBooksMessage 依賴於 this.author.books,因此當 this.author.books 發生變化時,它會更新所有依賴於 this.publishedBooksMessage 的繫結。

參見: 計算屬性的型別化

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>

在沙箱中嘗試

這裡我們聲明瞭一個計算屬性 publishedBooksMessagecomputed() 函式期望傳入一個 getter 函式,返回值是一個 計算引用。類似於普通的引用,你可以透過 publishedBooksMessage.value 訪問計算結果。計算引用在模板中也會自動展開,所以你可以在模板表示式中直接引用它們而不需要 .value

計算屬性會自動跟蹤它的響應式依賴。Vue 知道 publishedBooksMessage 的計算依賴於 author.books,因此當 author.books 發生變化時,它將更新任何依賴於 publishedBooksMessage 的繫結。

另請參閱:計算屬性型別

計算屬性快取與方法的比較

你可能已經注意到,我們可以透過在表示式中呼叫一個方法來實現相同的結果

template
<p>{{ calculateBooksMessage() }}</p>
js
// in component
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}
js
// in component
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Yes' : 'No'
}

與計算屬性不同,我們可以將相同的函式定義為方法。對於最終結果,這兩種方法實際上是完全相同的。然而,區別在於 計算屬性是基於它們的響應式依賴進行快取的。 計算屬性只有在某些響應式依賴發生變化時才會重新評估。這意味著只要 author.books 沒有變化,多次訪問 publishedBooksMessage 將立即返回之前計算的結果,而不需要再次執行 getter 函式。

這也意味著以下計算屬性永遠不會更新,因為 Date.now() 不是一個響應式依賴

js
computed: {
  now() {
    return Date.now()
  }
}
js
const now = computed(() => Date.now())

相比之下,方法呼叫將在每次重新渲染時始終執行函式。

為什麼我們需要快取?想象我們有一個昂貴的計算屬性 list,它需要遍歷一個巨大的陣列並進行大量的計算。然後我們可能有其他依賴於 list 的計算屬性。如果沒有快取,我們可能需要執行 list 的 getter 比必要的次數多得多!在你不想快取的情況下,可以使用方法呼叫。

可寫計算屬性

計算屬性預設是隻讀的。如果你嘗試為計算屬性分配新值,你將收到一個執行時警告。在極少數需要“可寫”計算屬性的情況下,你可以透過提供 getter 和 setter 來建立一個

js
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        // Note: we are using destructuring assignment syntax here.
        [this.firstName, this.lastName] = newValue.split(' ')
      }
    }
  }
}

現在當執行 this.fullName = 'John Doe' 時,setter 將被呼叫,並且 this.firstNamethis.lastName 將相應地更新。

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 將被呼叫,並且 firstNamelastName 將相應地更新。

獲取上一個值

  • 僅支援 3.4+

如果你需要,可以透過訪問 getter 的第一個引數來獲取計算屬性返回的上一個值

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    // 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
    alwaysSmall(previous) {
      if (this.count <= 3) {
        return this.count;
      }

      return previous;
    }
  }
}
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>

如果你正在使用可寫計算

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    alwaysSmall: {
      get(previous) {
        if (this.count <= 3) {
          return this.count;
        }

        return previous;
      },
      set(newValue) {
        this.count = newValue * 2;
      }
    }
  }
}
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! 將計算屬性視為基於其他值宣告性地描述如何推匯出一個值——其唯一責任應該是計算並返回該值。在指南的後面部分,我們將討論如何使用觀察者來執行對狀態變化的響應。

避免修改計算值

計算屬性返回的值是從衍生狀態推匯出來的。將其視為一個臨時快照——每次源狀態發生變化時,都會建立一個新的快照。修改快照是沒有意義的,因此計算屬性返回的值應被視為只讀的,並且不得修改——相反,應該更新它所依賴的源狀態來觸發新的計算。

已載入計算屬性