Vue3 教學:模板語法中的屬性縮寫(:, @)
簡介
在 Vue 3 的單檔組件(.vue)裡,**模板(template)**是與使用者互動的第一層介面。若沒有良好的模板寫法,程式碼很快就會變得冗長且難以維護。Vue 為了讓模板更簡潔,提供了兩個常用的 屬性縮寫:
:→v-bind(綁定屬性或動態值)@→v-on(綁定事件)
這兩個縮寫不僅能減少鍵入次數,還能提升可讀性,讓開發者專注於 「做什麼」 而非 「怎麼寫」。本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,最後帶入實務應用,完整掌握屬性縮寫的使用方法。
核心概念
1. :(v‑bind)—— 動態綁定屬性
v-bind 用於把 JavaScript 表達式的值綁定到 HTML 屬性上。縮寫 : 只是一個語法糖,功能完全相同。
<!-- 完整寫法 -->
<img v-bind:src="imageUrl" v-bind:alt="imageAlt">
<!-- 縮寫寫法 -->
<img :src="imageUrl" :alt="imageAlt">
- 靜態屬性 vs 動態屬性
- 靜態屬性(寫死的字串)直接寫在標籤上即可。
- 動態屬性需要透過
:或v-bind,讓 Vue 在渲染時把資料變更同步到 DOM。
1.1 綁定 class 與 style
Vue 允許以物件或陣列形式綁定 class 與 style,縮寫寫法同樣適用。
<div :class="{ active: isActive, disabled: isDisabled }"></div>
<div :style="{ color: textColor, fontSize: fontSize + 'px' }"></div>
1.2 綁定自訂屬性(props)
在子組件上傳遞資料時,常會使用 : 直接綁定。
<!-- 父層 -->
<my-button :label="btnLabel" :disabled="isBtnDisabled"></my-button>
2. @(v‑on)—— 事件監聽
v-on 用於在模板中監聽 DOM 事件或自訂事件。縮寫 @ 讓事件綁定更為簡潔。
<!-- 完整寫法 -->
<button v-on:click="handleClick">Click Me</button>
<!-- 縮寫寫法 -->
<button @click="handleClick">Click Me</button>
- 事件修飾符(
.stop、.prevent、.once、.capture…)同樣可以與@結合。
<form @submit.prevent="onSubmit">
<input @keyup.enter="onEnter" />
</form>
2.1 監聽自訂事件
子組件透過 emit 發出事件,父層使用 @ 監聽。
<!-- 子組件 MyModal.vue -->
<template>
<div class="modal">
<button @click="$emit('close')">關閉</button>
</div>
</template>
<!-- 父層使用 -->
<my-modal @close="showModal = false"></my-modal>
3. 同時使用 : 與 @
在同一個標籤上綁定屬性與事件時,兩者可以自由混用,保持語意清晰。
<my-input
:value="searchQuery"
@input="searchQuery = $event"
@focus="onFocus"
placeholder="搜尋關鍵字"
></my-input>
程式碼範例
以下提供 5 個實用範例,說明屬性縮寫在不同情境下的寫法與注意點。
範例 1:動態圖片切換
<template>
<div>
<img :src="currentImg" :alt="`圖片 ${index + 1}`" />
<button @click="nextImg">下一張</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const images = [
'https://picsum.photos/id/1015/400/200',
'https://picsum.photos/id/1016/400/200',
'https://picsum.photos/id/1018/400/200'
]
const index = ref(0)
const currentImg = computed(() => images[index.value])
function nextImg() {
index.value = (index.value + 1) % images.length
}
</script>
重點:
:讓src、alt能即時反映index的變化;@click觸發切換邏輯。
範例 2:條件式 class 與事件修飾
<template>
<button
:class="[{ primary: isPrimary }, 'rounded']"
@click.stop="togglePrimary"
>
{{ isPrimary ? '主題模式' : '一般模式' }}
</button>
</template>
<script setup>
import { ref } from 'vue'
const isPrimary = ref(false)
function togglePrimary() {
isPrimary.value = !isPrimary.value
}
</script>
<style scoped>
.primary {
background-color: #42b983;
color: white;
}
.rounded {
border-radius: 8px;
}
</style>
說明:
:以陣列方式混合靜態與動態 class;@click.stop防止事件冒泡。
範例 3:表單驗證的 v-model 與 @submit.prevent
<template>
<form @submit.prevent="onSubmit">
<input
type="email"
v-model="email"
:class="{ error: emailError }"
placeholder="請輸入 Email"
/>
<span v-if="emailError" class="msg">Email 格式不正確</span>
<button type="submit">送出</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const email = ref('')
const emailError = ref(false)
function onSubmit() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
emailError.value = !pattern.test(email.value)
if (!emailError.value) {
alert(`送出成功:${email.value}`)
}
}
</script>
<style scoped>
.error {
border: 1px solid red;
}
.msg {
color: red;
font-size: 0.9rem;
}
</style>
關鍵:
@submit.prevent取代了event.preventDefault(),讓表單提交行為更直觀。
範例 4:子組件向父層傳遞資料(自訂事件)
<!-- ChildCounter.vue -->
<template>
<button @click="increment">+ {{ count }}</button>
</template>
<script setup>
import { defineEmits, ref } from 'vue'
const emit = defineEmits(['update'])
const count = ref(0)
function increment() {
count.value++
emit('update', count.value) // 向父層傳遞最新值
}
</script>
<!-- Parent.vue -->
<template>
<h2>目前計數:{{ total }}</h2>
<ChildCounter @update="total = $event" />
</template>
<script setup>
import { ref } from 'vue'
import ChildCounter from './ChildCounter.vue'
const total = ref(0)
</script>
說明:父層使用
@update直接接收子層傳遞的參數,省去繁瑣的$event變數命名。
範例 5:使用 : 於 v-bind 目標屬性(SVG)
<template>
<svg width="120" height="120" viewBox="0 0 120 120">
<circle
:cx="centerX"
:cy="centerY"
:r="radius"
:stroke="color"
stroke-width="4"
fill="none"
/>
</svg>
<input type="range" min="10" max="50" v-model="radius" />
</template>
<script setup>
import { ref } from 'vue'
const centerX = 60
const centerY = 60
const radius = ref(30)
const color = ref('#ff5722')
</script>
重點:即使是 SVG 屬性,
:仍能正確綁定,使圖形隨radius變化而即時更新。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 建議的解決方式 |
|---|---|---|
| 忘記加引號 | :src=imageUrl 會被解析為 屬性名稱 而非綁定表達式,導致錯誤。 |
必須寫成 :src="imageUrl"(包含雙引號)。 |
| 使用保留字作屬性 | Vue 內建指令如 class、style 已有特殊處理,若使用 :class 時不小心寫成 :class="class" 會產生衝突。 |
改用不同變數名稱(className、customClass),或直接使用物件語法。 |
| 事件名稱大小寫不一致 | 在 HTML 中屬性名不區分大小寫,@myEvent 會被轉成 myevent,子組件若 $emit('myEvent'),父層無法接收。 |
使用 kebab-case(@my-event)或 全部小寫,保持一致。 |
| 過度使用縮寫 | 雖然縮寫簡潔,但過度濫用會讓新手難以辨識 v-bind 與 v-on 的功能。 |
在較複雜的模板或教學文件中,適度保留完整寫法提升可讀性。 |
忘記 .prevent、.stop |
表單提交或點擊冒泡時,若未加修飾符會導致意外行為。 | 依需求加入 @submit.prevent、@click.stop 等修飾符。 |
最佳實踐
- 保持一致的命名風格:建議在整個專案中統一使用
:與@的縮寫,並採用kebab-case事件名稱。 - 適度加入註解:在較複雜的綁定(如動態
class、style)旁加上簡短註解,方便團隊成員快速理解。 - 分離邏輯與模板:僅在模板裡使用簡單表達式,較複雜的計算或資料轉換請放到
computed或methods中,保持模板乾淨。 - 善用 TypeScript:若使用 Vue 3 + TS,為
props、emit定義類型,能在編譯階段捕捉錯誤,減少屬性縮寫的誤用。
實際應用場景
1. 動態表單生成
在大型企業內部系統,表單欄位往往依據後端配置動態產生。透過 : 可把欄位的 type、placeholder、required 等屬性直接綁定到 JSON 配置,減少硬編碼。
<div v-for="field in formConfig" :key="field.id">
<input
:type="field.type"
:placeholder="field.placeholder"
:required="field.required"
v-model="formData[field.name]"
/>
</div>
2. 互動式圖表與視覺化
在使用 echarts、d3 或原生 SVG 時,常需要根據使用者操作即時改變屬性(顏色、座標、大小)。: 能把資料驅動的屬性直接映射到圖形上,讓 UI 變化即時且流暢。
3. 複雜的父子通訊
大型專案的元件樹結構深,子層往往需要向上回報多個事件(如表單驗證結果、選取狀態)。使用 @ 加上自訂事件名稱,可讓父層清晰地監聽每個子層行為,保持單向資料流的概念。
4. 多語系切換
語系文字往往放在 i18n 物件中,使用 : 直接綁定文字來源,讓切換語系時不必手動更新每個字串。
<h1 :title="$t('page.title')">{{ $t('page.heading') }}</h1>
總結
- 屬性縮寫
:(v-bind)與@(v-on)是 Vue 3 模板語法的核心工具,能讓屬性與事件的綁定變得簡潔且易於維護。 - 正確使用縮寫能提升 可讀性、開發效率,並保持 單向資料流 的設計原則。
- 在使用過程中要留意 引號、命名衝突、大小寫 等常見陷阱,並遵循 一致的命名風格、分離邏輯與模板 的最佳實踐。
- 無論是 動態表單、互動圖表、父子通訊 或 多語系切換,屬性縮寫都是讓 Vue 應用更具彈性與可擴充性的關鍵技巧。
掌握了這兩個縮寫,你就可以在 Vue 3 中寫出 乾淨、易懂、具備高度互動性的 UI,為專案的可維護性奠定堅實基礎。祝你在 Vue 的世界裡玩得開心,寫出更好的程式碼!