Appearance
事件處理與表單綁定
畫面能顯示資料之後,下一步就是讓使用者操作畫面。Vue 的事件處理用 v-on,常見縮寫是 @;表單資料同步則用 v-model。這一節先處理 DOM 事件與原生表單元素,元件之間的 v-model 與 defineModel 會留到 CH8。
監聽事件
最基本的事件處理,是在模板上用 @click 指向一個函式:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<main class="app">
<p>目前點擊次數:{{ count }}</p>
<button type="button" @click="increment">
點我
</button>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>@click="increment" 的意思是:當按鈕被點擊時,呼叫 increment()。注意模板中只需要寫函式名稱即可,除非要傳入參數,否則不必加上括號。
傳入事件物件與參數
如果需要取得原生事件物件,可以直接接收參數:
vue
<script setup>
import { ref } from 'vue'
const lastKey = ref('')
function recordKey(event) {
lastKey.value = event.key
}
</script>
<template>
<main class="app">
<input type="text" @keyup="recordKey">
<p>最後按下的鍵:{{ lastKey }}</p>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>如果同時需要自訂參數與事件物件,可以使用 $event:
vue
<script setup>
import { ref } from 'vue'
const message = ref('')
function submit(source, event) {
message.value = `${source} 送出:${event.target.value}`
}
</script>
<template>
<main class="app">
<input
type="text"
placeholder="輸入後按 Enter"
@keyup.enter="submit('鍵盤', $event)"
>
<p>{{ message }}</p>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>事件修飾子
在原生 DOM 事件裡,我們常用 event.preventDefault() 阻止預設行為,也會用 event.stopPropagation() 擋住事件繼續往父層冒泡。Vue 把這兩種常見操作做成了 .prevent 與 .stop 修飾子:
vue
<script setup>
import { ref } from 'vue'
const submitted = ref(false)
function submit() {
submitted.value = true
}
</script>
<template>
<main class="app">
<form class="form" @submit.prevent="submit">
<button type="submit">
送出
</button>
</form>
<p v-if="submitted">表單已送出,但頁面沒有重新整理。</p>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
.form {
margin-bottom: 1rem;
}
</style>表單的預設行為是送出後重新整理(或跳轉)頁面,範例中的 @submit.prevent 阻止了這件事,所以送出後畫面上的訊息能留在原地。
常見事件修飾子如下:
| 修飾子 | 用途 |
|---|---|
.prevent | 呼叫 event.preventDefault(),阻止元素的預設行為(如表單送出後重新整理頁面)。 |
.stop | 呼叫 event.stopPropagation(),阻止事件往外層元素冒泡。 |
.once | 事件只觸發一次。 |
.self | 只有事件來自元素本身時才觸發。 |
按鍵修飾子
鍵盤事件可以搭配按鍵修飾子,讓模板更接近需求本身:
vue
<script setup>
import { ref } from 'vue'
const keyword = ref('')
const searched = ref('')
function search() {
searched.value = keyword.value
}
</script>
<template>
<main class="app">
<input
v-model="keyword"
type="search"
placeholder="輸入關鍵字後按 Enter"
@keyup.enter="search"
>
<p>搜尋關鍵字:{{ searched }}</p>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>像 .enter、.esc、.tab 這類修飾子,可以少寫一層 if (event.key === 'Enter')。
原生表單元素的 v-model
表單最常見的需求,是讓輸入值與資料保持同步。v-model 可以把這件事變得很直覺:
vue
<script setup>
import { ref } from 'vue'
const name = ref('')
</script>
<template>
<main class="app">
<label>
名字
<input v-model="name" type="text">
</label>
<p>你好,{{ name || '訪客' }}!</p>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
</style>v-model="name" 會把輸入框的值同步到 name,也會在 name 改變時更新輸入框。對原生表單元素來說,它可以用在 input、textarea、select 等元素上。
vue
<script setup>
import { ref } from 'vue'
const note = ref('')
const level = ref('beginner')
</script>
<template>
<main class="app">
<label>
學習筆記
<textarea v-model="note"></textarea>
</label>
<label>
程度
<select v-model="level">
<option value="beginner">初學</option>
<option value="intermediate">有經驗</option>
<option value="advanced">進階</option>
</select>
</label>
<p>目前程度:{{ level }}</p>
<p>筆記:{{ note }}</p>
</main>
</template>
<style scoped>
.app {
display: grid;
gap: 1rem;
padding: 2rem;
}
label {
display: grid;
gap: 0.5rem;
}
</style>checkbox 與 radio
v-model 遇到不同表單元素,綁定方式也不同:文字欄位跟著 value 走;單一 checkbox 代表 true 或 false;radio 則取得被選中選項的 value。這些差異 Vue 會替我們處理,不需要自己分辨。
vue
<script setup>
import { ref } from 'vue'
const subscribed = ref(false)
const plan = ref('free')
</script>
<template>
<main class="app">
<label>
<input v-model="subscribed" type="checkbox">
訂閱電子報
</label>
<fieldset>
<legend>選擇方案</legend>
<label>
<input v-model="plan" type="radio" value="free">
免費方案
</label>
<label>
<input v-model="plan" type="radio" value="pro">
付費方案
</label>
</fieldset>
<p>{{ subscribed ? '已訂閱' : '未訂閱' }},方案:{{ plan }}</p>
</main>
</template>
<style scoped>
.app {
display: grid;
gap: 1rem;
padding: 2rem;
}
</style>subscribed 是布林值,勾選與取消勾選會在 true 與 false 之間切換;plan 則永遠等於目前被選中的 radio 的 value。這裡先掌握單一 checkbox;多選 checkbox 綁定陣列的情境,留到 CH8 表單章再一起處理。
v-model 修飾子
原生表單元素的 v-model 也有幾個常用修飾子:
| 修飾子 | 用途 |
|---|---|
.lazy | 等到 change 事件才同步,不是每次輸入都同步。 |
.number | 嘗試把輸入值轉成數字。 |
.trim | 自動移除前後空白。 |
vue
<script setup>
import { ref } from 'vue'
const title = ref('')
const quantity = ref(1)
</script>
<template>
<main class="app">
<label>
標題
<input v-model.trim="title" type="text">
</label>
<label>
數量
<input v-model.number="quantity" type="number" min="1">
</label>
<p>{{ title || '未命名' }}:{{ quantity }} 份</p>
</main>
</template>
<style scoped>
.app {
display: grid;
gap: 1rem;
padding: 2rem;
}
label {
display: grid;
gap: 0.5rem;
}
</style>小提醒:v-model 也能用在元件上
這一節只討論原生表單元素的 v-model。父子元件之間的 v-model、defineModel、表單驗證與更完整的資料流設計,CH8「表單與資料流」會有更詳細的說明。
