Skip to content

事件處理與表單綁定

重新認識 Vue.js: 008 天絕對看不完的 Vue.js 3.x 指南

畫面能顯示資料之後,下一步就是讓使用者操作畫面。Vue 的事件處理用 v-on,常見縮寫是 @;表單資料同步則用 v-model。這一節先處理 DOM 事件與原生表單元素,元件之間的 v-modeldefineModel 會留到 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 改變時更新輸入框。對原生表單元素來說,它可以用在 inputtextareaselect 等元素上。

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 代表 truefalse;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 是布林值,勾選與取消勾選會在 truefalse 之間切換;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-modeldefineModel、表單驗證與更完整的資料流設計,CH8「表單與資料流」會有更詳細的說明。