本文 AI 產出,尚未審核

Vue 3:模板語法 – 範本內運算式與過濾器(filters 已移除)

簡介

在 Vue 3 中,模板 (template) 是與使用者直接互動的最前線。開發者透過模板將資料、事件與 UI 串接起來,而 範本內運算式 (template expressions) 則是讓資料在渲染前做簡單處理的利器。過去 Vue 2 提供的 filters 功能在 Vue 3 已正式 移除,改為鼓勵使用 方法 (methods) 或 computed,甚至是 自訂指令 來完成相同需求。

了解如何在模板中安全、有效地使用運算式,並掌握取代 filters 的新寫法,對於提升程式碼可讀性、維護性以及效能都有直接影響。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶領讀者一步步在 Vue 3 中玩轉模板運算式。


核心概念

1. 範本內運算式的基本規則

  • 只能是單行 JavaScript 表達式,不允許使用語句 (statement) 如 if…elseforswitch 等。
  • 不支援賦值或副作用{{ count = 1 }} 會在開發模式警告,因為模板的目的是「呈現」而非「改變」資料。
  • 只能存取組件實例的屬性datapropscomputedmethodssetup 回傳的變數皆可直接使用。
  • 安全性考量:運算式會在 Vue 的渲染函式內執行,若寫入過於複雜的邏輯,會影響效能與除錯難度。

2. 為什麼 Vue 3 移除 filters?

Vue 2 的 filters Vue 3 的取代方案
只能在插值 {{ }} 或指令參數中使用 使用 methodscomputed自訂指令
只能返回字串,無法接受多參數 方法可以接受任意參數,返回任意類型
會讓模板混入過多格式化邏輯,降低可讀性 把格式化邏輯集中到 JavaScript 層,讓模板保持「純粹」

重點:在 Vue 3 中,若需要類似 {{ price | currency }} 的功能,請改寫為 {{ formatCurrency(price) }},或在 computed 中預先處理。

3. 運算式的三大使用情境

  1. 文字插值{{ message.toUpperCase() }}
  2. 屬性綁定:class="{ active: isActive }":src="imageUrl + '?v=' + version"
  3. 事件處理@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 }}
  • taxedPricecomputed,避免在模板每次渲染時重新計算。

範例 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 的模板保持單行、易讀。
  • 若格式化過程較重,可改寫成 computedmemoized 函式,以減少不必要的重新計算。

常見陷阱與最佳實踐

陷阱 可能的後果 推薦的做法
在模板內寫過長的運算式(如多層嵌套的三元或函式呼叫) 可讀性下降、除錯困難,且每次渲染都會重新計算 把複雜邏輯搬到 methodscomputedsetup
使用副作用運算式(如 {{ count = 0 }} 破壞單向資料流,開發模式會警告 僅在 JavaScript 層處理資料變更,模板只負責展示
把過濾器 (filters) 的概念直接搬到模板 Vue 3 不支援,會產生錯誤 改用方法或自訂指令,或在 computed 中預先處理
v-for 內直接呼叫重計算函式(如 {{ heavyCalc(item) }} 每次渲染都會觸發重運算,效能受損 使用 computed 搭配 key 或 memoization 方案
在插值中使用 new Date()Math.random() 等非純函式 每次渲染結果不同,導致 UI 不穩定 把這類值搬到 datasetup,只在需要時更新

最佳實踐清單

  1. 保持模板純粹:只做「顯示」與「簡單運算」;所有業務邏輯放在 JavaScript 層。
  2. 利用 computed 進行衍生資料:自動緩存、避免不必要的重算。
  3. 方法 (methods) 用於一次性或事件觸發的運算:如格式化、過濾、排序等。
  4. 自訂指令:適用於需要直接操作 DOM 或在多處使用的格式化需求。
  5. 遵守單行表達式規則:避免使用 if…elseswitchfor 等語句。
  6. 型別安全:在 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 已被移除,取而代之的是 methodscomputed、以及 自訂指令,讓程式碼更具可測試性與可維護性。
  • 最佳實踐:將任何超過簡單字串拼接或算術的邏輯抽離到 JavaScript 層,使用 computed 進行緩存,使用 methods 處理一次性或事件驅動的運算,使用指令完成 DOM 直接操作或全域格式化需求。
  • 避免在模板中寫過長、帶副作用或使用語句的程式碼,這不僅會降低可讀性,也會影響效能。

透過本文的概念說明與實作範例,你應該已能在 Vue 3 中自信地使用模板運算式,同時把過去的 filter 功能順利遷移到更現代、更彈性的寫法。未來在開發大型專案或團隊協作時,保持「模板只負責顯示」的原則,將讓程式碼更乾淨、維護成本更低,也能更容易迎接 Vue 生態系的持續演進。祝開發順利,玩得開心!