Vue3 – Options API(傳統寫法)
主題:computed
簡介
在 Vue3 中,computed 是最常用的衍生(derived)資料來源之一。它可以根據其他反應式(reactive)狀態自動計算出新值,且具備 快取(caching) 機制,只有當相依的來源發生變化時才會重新求值。對於需要在模板中顯示衍生資料、或在多個地方共用相同計算結果的情境,computed 是不可或缺的工具。
即使在 Vue3 推出 Composition API,Options API 仍是許多既有專案與新手入門的首選寫法。掌握 computed 的使用方式,能讓你在不改變既有結構的前提下,寫出更簡潔、效能更佳的程式碼。
核心概念
1. computed 的基本語法
在 Options API 中,computed 必須在 export default {} 的 computed 屬性裡宣告,回傳一個函式或一個具有 get / set 的物件。
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
// 只讀 computed
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
}
- 快取:
fullName只會在firstName或lastName改變時重新計算,其他 re‑render 不會觸發函式執行。 - 依賴追蹤:Vue 會自動追蹤
this.firstName、this.lastName兩個依賴。
2. 具備 Setter 的 Computed(雙向綁定)
有時候需要讓 computed 既能讀也能寫,這時可以提供 set 方法。
export default {
data() {
return {
firstName: '',
lastName: ''
}
},
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`.trim()
},
set(value) {
const parts = value.split(' ')
this.firstName = parts[0] || ''
this.lastName = parts.slice(1).join(' ') || ''
}
}
}
}
使用情境:在表單中直接綁定
v-model="fullName",使用者編輯完整姓名時,背後的firstName、lastName也會同步更新。
3. 多層依賴的 Computed
computed 可以相互依賴,形成「計算鏈」。只要最底層的依賴變動,整條鏈都會重新計算。
export default {
data() {
return {
numbers: [1, 2, 3, 4, 5]
}
},
computed: {
// 先過濾出偶數
evenNumbers() {
return this.numbers.filter(n => n % 2 === 0)
},
// 再計算偶數總和
sumEven() {
return this.evenNumbers.reduce((acc, n) => acc + n, 0)
}
}
}
- 當
numbers被推入新元素時,evenNumbers重新計算,接著sumEven也會自動更新。
4. Computed 與方法(method)的差別
computed |
method |
|
|---|---|---|
| 快取 | 有(除非依賴變更) | 無,每次呼叫都會執行 |
| 適用情境 | 需要重複使用相同結果的衍生資料 | 僅在一次性操作或副作用較重的情況 |
| 語意 | 「這是一個值」 | 「這是一個行為」 |
範例:若只在模板中顯示過濾後的清單,使用
computed能避免每次渲染都重新過濾。
5. 使用 computed 產生「懶」的非同步資料(進階技巧)
computed 本身是同步的,但可以結合 Promise 或 async 函式,搭配 watchEffect 或 watch 產生「懶」的非同步結果。
export default {
data() {
return {
userId: 1,
userInfo: null
}
},
computed: {
// 產生一個 Promise,只有在 userId 改變時才會重新執行
userPromise() {
return fetch(`https://jsonplaceholder.typicode.com/users/${this.userId}`)
.then(res => res.json())
}
},
watch: {
// 監聽 computed 回傳的 Promise,解析後寫入 data
userPromise: {
immediate: true,
handler(promise) {
promise.then(data => {
this.userInfo = data
})
}
}
}
}
- 雖然不建議把大量非同步邏輯放在
computed,但在「依賴變動才需要重新抓資料」的情況下,此技巧可以減少不必要的 API 呼叫。
程式碼範例
以下提供 5 個實用範例,每個範例皆包含完整說明與常見應用。
範例 1:簡易的字串拼接(只讀)
export default {
data() {
return {
firstName: 'Jane',
lastName: 'Smith'
}
},
computed: {
// **只讀** computed,用於顯示完整姓名
fullName() {
return `${this.firstName} ${this.lastName}`
}
},
// template
// <p>姓名:{{ fullName }}</p>
}
說明:
fullName只會在firstName或lastName改變時重新計算,適合在多處顯示同一資訊。
範例 2:雙向綁定的完整地址
export default {
data() {
return {
street: '',
city: '',
zip: ''
}
},
computed: {
address: {
// 讀取完整地址
get() {
return `${this.street}, ${this.city} ${this.zip}`.replace(/^, | ,/g, '')
},
// 設定時自動拆解回各欄位
set(value) {
const [street, cityZip] = value.split(',')
this.street = street?.trim() || ''
const [city, zip] = cityZip?.trim().split(' ') || []
this.city = city?.trim() || ''
this.zip = zip?.trim() || ''
}
}
}
// template
// <input v-model="address" placeholder="請輸入完整地址">
}
關鍵:透過
set,表單只需要一個v-model,卻能同時更新多個資料屬性。
範例 3:過濾與統計(多層依賴)
export default {
data() {
return {
products: [
{ name: '筆記型電腦', price: 35000, category: '電子' },
{ name: '滑鼠', price: 1200, category: '電子' },
{ name: '書桌', price: 8000, category: '家具' },
// …更多資料
],
filterCategory: '電子'
}
},
computed: {
// 先依類別過濾
filteredProducts() {
return this.products.filter(p => p.category === this.filterCategory)
},
// 再計算總金額
totalPrice() {
return this.filteredProducts.reduce((sum, p) => sum + p.price, 0)
},
// 最後產生可顯示的文字
summary() {
return `類別 ${this.filterCategory} 共有 ${this.filteredProducts.length} 件商品,總價 $${this.totalPrice}`
}
}
}
實務:在電商後台常用此方式快速切換類別統計,
computed的快取讓切換 UI 時不會重算所有商品。
範例 4:日期格式化(依賴外部函式)
import { format } from 'date-fns' // npm 安裝 date-fns
export default {
data() {
return {
rawDate: new Date()
}
},
computed: {
// 使用第三方函式格式化日期
formattedDate() {
// **注意**:format 本身是純函式,不會改變 rawDate
return format(this.rawDate, 'yyyy/MM/dd HH:mm')
}
}
}
說明:即使
format是外部函式,只要傳入的參數是響應式的(rawDate),computed 仍會正確追蹤變化。
範例 5:懶載入使用者資料(結合 watch)
export default {
data() {
return {
userId: 2,
userInfo: null,
loading: false,
error: null
}
},
computed: {
// 只要 userId 改變,就產生新的 Promise
userPromise() {
this.loading = true
this.error = null
return fetch(`https://jsonplaceholder.typicode.com/users/${this.userId}`)
.then(r => r.ok ? r.json() : Promise.reject('Network error'))
}
},
watch: {
// 當 computed 回傳的 Promise 完成時,寫入 userInfo
userPromise: {
immediate: true,
handler(promise) {
promise
.then(data => {
this.userInfo = data
})
.catch(err => {
this.error = err
})
.finally(() => {
this.loading = false
})
}
}
}
}
最佳實踐:把「依賴變動才發送請求」的邏輯放在
computed,再用watch處理非同步結果,避免在mounted中寫冗長的條件判斷。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解法 / 最佳實踐 |
|---|---|---|
把副作用寫在 computed 內 |
computed 應該是純函式,不能直接呼叫 API、改變資料或觸發 UI。 |
使用 watch 或 watchEffect 處理副作用。 |
| 忽略快取機制 | 當依賴是 物件/陣列,若只是變更內部屬性(如 push),computed 仍會偵測到,因 Vue 內部使用 Proxy。 |
若使用 非響應式 物件(如 Object.freeze),需要手動觸發更新。 |
在 computed 中使用大量計算 |
每次依賴變更都會重新執行,若演算法過於昂貴會影響效能。 | 把重度計算抽成 純函式,或使用 memoization(如 lodash.memoize)。 |
忘記 return |
在 Options API 中,computed 必須回傳值,漏寫 return 會得到 undefined。 |
確認每個 getter 都有 return,或使用簡寫語法 fullName() { return ... }。 |
在 set 中直接改變依賴 |
若 set 內部又觸發另一個 computed,可能造成 無限循環。 |
確保 set 只改變 source data,不要直接寫入其他 computed。 |
最佳實踐:
- 保持純函式:
computed只做「計算」不做「副作用」。 - 適度拆分:把大型計算拆成多個小的 computed,利用快取鏈提升效能。
- 使用
getter / setter只在需要雙向綁定時,否則使用只讀形式即可。 - 命名一致:以
xxxList、xxxCount、xxxFormatted等後綴明確表示回傳類型。 - 測試快取行為:開發時可在 getter 裡
console.log,觀察是否因不必要的依賴而頻繁執行。
實際應用場景
表單資料合併
多個輸入欄位(如姓名、電話、地址)需要在提交前組成一個 JSON 物件,使用computed可以即時預覽合併結果,減少手動拼接的錯誤。資料表格的分頁、排序與過濾
filteredData:根據搜尋關鍵字過濾sortedData:根據欄位排序pagedData:根據目前頁碼切割
這三層 computed 可以保證每次只重新計算受影響的那一層。
國際化(i18n)字串
依賴當前語系 (locale) 的 computed 可自動返回對應的翻譯文字,無需在模板裡寫繁雜的條件判斷。圖表資料的即時轉換
從原始 API 回傳的資料陣列,透過 computed 產生符合 Chart.js、ECharts 等套件所需的labels/datasets結構,讓圖表渲染保持同步且效能佳。權限判斷
依據使用者角色 (user.role) 計算出可操作的功能清單(allowedActions),在 UI 中直接以v-if="allowedActions.includes('edit')"控制顯示。
總結
computed是 Options API 中用來產生「衍生」資料的核心工具,具備 快取、依賴追蹤 兩大特性。- 只讀形式適合顯示或重複使用的資料;具
get/set的雙向綁定則能簡化表單與資料同步的程式碼。 - 多層依賴、外部函式、甚至懶載入非同步資料,都可以透過
computed搭配watch來實現彈性且效能友好的解決方案。 - 避免在
computed中寫副作用、過度計算或無意的循環,遵守「純函式」原則與適當的命名規則,能讓程式碼更易維護。
掌握了以上概念與實務範例,你就能在 Vue3 的 Options API 中,利用 computed 建構出高效、可讀、易維護的前端應用。祝開發順利! 🚀