本文 AI 產出,尚未審核
Vue 3:模板語法 – 範本內運算式與過濾器(filters 已移除)
簡介
在 Vue 3 中,模板 (template) 是與使用者直接互動的最前線。開發者透過模板將資料、事件與 UI 串接起來,而 範本內運算式 (template expressions) 則是讓資料在渲染前做簡單處理的利器。過去 Vue 2 提供的 filters 功能在 Vue 3 已正式 移除,改為鼓勵使用 方法 (methods) 或 computed,甚至是 自訂指令 來完成相同需求。
了解如何在模板中安全、有效地使用運算式,並掌握取代 filters 的新寫法,對於提升程式碼可讀性、維護性以及效能都有直接影響。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶領讀者一步步在 Vue 3 中玩轉模板運算式。
核心概念
1. 範本內運算式的基本規則
- 只能是單行 JavaScript 表達式,不允許使用語句 (statement) 如
if…else、for、switch等。 - 不支援賦值或副作用:
{{ count = 1 }}會在開發模式警告,因為模板的目的是「呈現」而非「改變」資料。 - 只能存取組件實例的屬性:
data、props、computed、methods、setup回傳的變數皆可直接使用。 - 安全性考量:運算式會在 Vue 的渲染函式內執行,若寫入過於複雜的邏輯,會影響效能與除錯難度。
2. 為什麼 Vue 3 移除 filters?
| Vue 2 的 filters | Vue 3 的取代方案 |
|---|---|
只能在插值 {{ }} 或指令參數中使用 |
使用 methods、computed 或 自訂指令 |
| 只能返回字串,無法接受多參數 | 方法可以接受任意參數,返回任意類型 |
| 會讓模板混入過多格式化邏輯,降低可讀性 | 把格式化邏輯集中到 JavaScript 層,讓模板保持「純粹」 |
重點:在 Vue 3 中,若需要類似
{{ price | currency }}的功能,請改寫為{{ formatCurrency(price) }},或在computed中預先處理。
3. 運算式的三大使用情境
- 文字插值:
{{ message.toUpperCase() }} - 屬性綁定:
:class="{ active: isActive }"、:src="imageUrl + '?v=' + version" - 事件處理:
@click="count++"(簡單遞增仍屬於單行表達式)
程式碼範例
下面的範例以 單檔組件 (Single File Component, .vue) 為基礎,示範不同情境下的模板運算式與取代 filters 的寫法。
範例 1:文字插值與簡易格式化
<template>
<div>
<!-- 直接在插值中呼叫 methods -->
<p>原始金額:{{ price }} 元</p>
<p>格式化後:{{ formatCurrency(price) }}</p>
<!-- 使用 computed 事先處理 -->
<p>已加稅金額:{{ taxedPrice }} 元</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const price = ref(12345)
// 方法:接受任意參數,回傳字串
function formatCurrency(value) {
// Intl.NumberFormat 為國際化金額格式化工具
return new Intl.NumberFormat('zh-TW', {
style: 'currency',
currency: 'TWD'
}).format(value)
}
// computed:自動緩存,只要 price 改變才重新計算
const taxedPrice = computed(() => {
const taxRate = 0.05
return Math.round(price.value * (1 + taxRate))
})
</script>
說明:
formatCurrency取代了舊版的{{ price | currency }};taxedPrice為 computed,避免在模板每次渲染時重新計算。
範例 2:屬性綁定與三元運算子
<template>
<img
:src="avatarUrl + (isRetina ? '@2x' : '') + '.png'"
:alt="userName"
:class="['avatar', statusClass]"
/>
</template>
<script setup>
import { ref, computed } from 'vue'
const avatarUrl = ref('https://example.com/avatar')
const isRetina = ref(true)
const userName = ref('王小明')
const status = ref('online') // 可能值:online, offline, away
// 使用 computed 產生 class 名稱,保持模板簡潔
const statusClass = computed(() => {
return {
online: 'bg-green-500',
offline: 'bg-gray-400',
away: 'bg-yellow-400'
}[status.value] || ''
})
</script>
說明:
:src內的 三元運算子 (isRetina ? '@2x' : '') 完全符合「單行表達式」的規範。statusClass以 computed 包裝,使模板僅留下簡單的 class 綁定,易於維護。
範例 3:事件處理中的簡易運算式
<template>
<button @click="count++">點擊 +1({{ count }})</button>
<button @click="reset">重設</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
// 方法:重設計數器
function reset() {
count.value = 0
}
</script>
說明:
@click="count++"為合法的單行運算式,直接對ref進行遞增。- 若需要更複雜的邏輯(例如條件判斷),請改寫成方法。
範例 4:使用自訂指令取代過時的 filter
// directives/currency.js
export const vCurrency = {
beforeMount(el, binding) {
const locale = binding.arg || 'zh-TW'
const currency = binding.value || 'TWD'
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency
})
el.textContent = formatter.format(el.textContent)
},
// 當綁定值變動時重新格式化
updated(el, binding) {
const locale = binding.arg || 'zh-TW'
const currency = binding.value || 'TWD'
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency
})
el.textContent = formatter.format(el.textContent)
}
}
<template>
<!-- 使用自訂指令 v-currency 取代過去的 | currency -->
<p v-currency:en-US="'USD'">{{ rawPrice }}</p>
</template>
<script setup>
import { ref } from 'vue'
import { vCurrency } from './directives/currency.js'
const rawPrice = ref(9876)
// 全域或局部註冊指令
defineExpose({ rawPrice })
</script>
<script>
export default {
directives: {
currency: vCurrency
}
}
</script>
說明:
v-currency指令接受 參數(locale)與 修飾符(currency),彈性大於 filters。- 在 Vue 3 中,指令仍是官方支援的「模板層」功能,適合處理 DOM 直接操作或格式化需求。
範例 5:使用 setup 直接回傳函式,保持模板乾淨
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ formatItem(item) }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '蘋果', price: 30 },
{ id: 2, name: '香蕉', price: 12 },
{ id: 3, name: '橙子', price: 20 }
])
// 直接在 setup 中定義,回傳給模板使用
function formatItem(item) {
// 透過模板運算式把每筆資料轉成想要的字串
return `${item.name} - ${new Intl.NumberFormat('zh-TW').format(item.price)} 元`
}
</script>
說明:
- 把「字串拼接」的邏輯抽成
formatItem方法,讓v-for的模板保持單行、易讀。- 若格式化過程較重,可改寫成 computed 或 memoized 函式,以減少不必要的重新計算。
常見陷阱與最佳實踐
| 陷阱 | 可能的後果 | 推薦的做法 |
|---|---|---|
| 在模板內寫過長的運算式(如多層嵌套的三元或函式呼叫) | 可讀性下降、除錯困難,且每次渲染都會重新計算 | 把複雜邏輯搬到 methods、computed 或 setup 中 |
使用副作用運算式(如 {{ count = 0 }}) |
破壞單向資料流,開發模式會警告 | 僅在 JavaScript 層處理資料變更,模板只負責展示 |
| 把過濾器 (filters) 的概念直接搬到模板 | Vue 3 不支援,會產生錯誤 | 改用方法或自訂指令,或在 computed 中預先處理 |
在 v-for 內直接呼叫重計算函式(如 {{ heavyCalc(item) }}) |
每次渲染都會觸發重運算,效能受損 | 使用 computed 搭配 key 或 memoization 方案 |
在插值中使用 new Date()、Math.random() 等非純函式 |
每次渲染結果不同,導致 UI 不穩定 | 把這類值搬到 data 或 setup,只在需要時更新 |
最佳實踐清單
- 保持模板純粹:只做「顯示」與「簡單運算」;所有業務邏輯放在 JavaScript 層。
- 利用
computed進行衍生資料:自動緩存、避免不必要的重算。 - 方法 (methods) 用於一次性或事件觸發的運算:如格式化、過濾、排序等。
- 自訂指令:適用於需要直接操作 DOM 或在多處使用的格式化需求。
- 遵守單行表達式規則:避免使用
if…else、switch、for等語句。 - 型別安全:在 TypeScript 專案中,為每個方法與 computed 加上明確的回傳型別,提升 IDE 輔助與編譯時錯誤偵測。
實際應用場景
1. 電子商務商品列表
- 需求:顯示商品價格、折扣後金額、庫存狀態。
- 做法:使用
computed計算折扣價、methods格式化金額、v-if判斷庫存顏色。 - 好處:所有金額計算只在資料變更時重新執行,避免每次渲染重新跑算術。
2. 內容管理系統 (CMS) 的日期顯示
- 需求:文章發布時間需要依使用者語系顯示「多久前」或完整日期。
- 做法:在
setup中寫formatDate方法,接受Date物件與語系參數;在模板使用{{ formatDate(publishedAt) }}。 - 好處:日期格式化邏輯集中管理,未來更換國際化函式庫 (如 dayjs → date-fns) 時僅需修改方法。
3. 即時儀表板的數值千分位顯示
- 需求:大量數字需即時加千分位,且允許切換貨幣單位。
- 做法:建立
v-currency指令或formatNumber方法,配合ref的貨幣類型切換。 - 好處:指令自動在值變動時重新渲染,避免在每個插值中重複寫
Intl.NumberFormat。
4. 表單驗證訊息的動態顯示
- 需求:根據表單欄位的錯誤類型顯示不同訊息。
- 做法:在模板中使用
{{ errorMessage(field) }},errorMessage內部根據錯誤代碼返回對應文字。 - 好處:訊息文字集中管理,支援多語系,且模板仍保持單行呼叫。
總結
- 模板運算式 是 Vue 3 中展示資料的第一道關卡,只允許單行、無副作用的表達式。
- filters 已被移除,取而代之的是
methods、computed、以及 自訂指令,讓程式碼更具可測試性與可維護性。 - 最佳實踐:將任何超過簡單字串拼接或算術的邏輯抽離到 JavaScript 層,使用
computed進行緩存,使用methods處理一次性或事件驅動的運算,使用指令完成 DOM 直接操作或全域格式化需求。 - 避免在模板中寫過長、帶副作用或使用語句的程式碼,這不僅會降低可讀性,也會影響效能。
透過本文的概念說明與實作範例,你應該已能在 Vue 3 中自信地使用模板運算式,同時把過去的 filter 功能順利遷移到更現代、更彈性的寫法。未來在開發大型專案或團隊協作時,保持「模板只負責顯示」的原則,將讓程式碼更乾淨、維護成本更低,也能更容易迎接 Vue 生態系的持續演進。祝開發順利,玩得開心!