# 1-5 事件處理

事件是 JavaScript 的靈魂,一個具有高度互動性的網頁介面,對於各種事件的管理必定少不了。

在進入本篇之前,我們大多透過 v-model 指令來與 Vue 實體的資料溝通,但那畢竟只是語法糖的一種, Vue.js 甚至是 JavaScript 能做到的遠遠不僅於此。

本篇的內容將帶領各位讀者理解, Vue.js 如何來操作與管理網頁的事件。

# 事件綁定 - v-on

有別於前面一篇介紹了一堆與模板相關的指令,在 Vue.js 與事件有關係的指令就只有一個 v-on。 使用方式也很簡單,最常見的方式是 v-on:[事件名稱]="運算式" ,如:

const vm = Vue.createApp({
  data () {
    return {
      count: 0
    } 
  },
}).mount('#app');
1
2
3
4
5
6
7
<!-- 模板裡無需加上 this -->
<p>Count: {{ count }}</p>
<button v-on:click="count++">Plus</button>
1
2
3

或者也可以把 event handler function 寫在 methods 裡頭:

const vm = Vue.createApp({
  data () {
    return {
      count: 0
    } 
  },
  methods: {
    plus () {
      this.count++;
    }
  }
}).mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12
<p>Count: {{ count }}</p>
<button v-on:click="plus">Plus</button>
1
2
試一試

像這樣,我們將事件的處理器 (event handler) 事先在 methods 屬性宣告,然後透過 v-on 指令來綁定至對應的事件。

小提醒

v-bind 一樣, v-on 指令也提供了簡寫表示。

除了完整的 v-on:事件名稱="運算式" 寫法外,也可以簡寫成 @事件名稱="運算式",如:

<button v-on:click="alert('Hello!')">Click</button>
<button @click="alert('Hello!')">Click</button>
1
2

以上兩種寫法在執行時皆是同樣的結果。

# v-on 與 event 物件

在 JavaScript 裡,當監聽的事件發生時,EventListener 會去建立一個「事件物件」 (Event Object), 裡面包含了所有與這個事件有關的屬性,像是各種事件通用的屬性與方法、觸發事件的元素等等資訊。 像是大家很常用的 preventDefault()stopPropagation() 等都是由 event 物件所提供的方法。

那麼我們要如何在 v-onmehtods 取得這個 event 物件呢?

事實上, v-on 觸發事件時,如果沒有指定參數,預設就會將 event 物件當作參數傳入:

<button v-on:click="plus">Plus</button>
1
methods: {
  plus (event) {
    // "BUTTON"
    console.log(event.target.tagName);
  }
}
1
2
3
4
5
6

像這樣,程式在執行 plus 的時候,就可以取得預設傳入的 event 物件。

如果在 methods 需要傳入參數的情況,則可以在模板中指定傳入 $event 來指定 event 物件:




 

<p><input v-model.number="amount"></p>
<p>Count: {{ count }}</p>

<button v-on:click="plus(amount, $event)">Plus</button>
1
2
3
4







 






data () {
  return {
    amount: 0,
    count: 0
  }
},
methods: {
  plus (amount, event) {    
    // "BUTTON"
    console.log(event.target.tagName);
    this.count += amount;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

像這樣在 methods 就可以取得 event 物件了。

# v-on 與修飾子

v-model 一樣, Vue.js 針對事件的 v-on 指令也提供了修飾子。

# 事件通用型修飾子

事件通用型修飾子主要有以下這些:

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

# .stop

.stop 的作用就如同大家熟知的 event.stopPropagation(),用來阻擋事件冒泡。

<div class="outer" @click="alert('Outer')">
  <span>Outer</span>
  <div class="inner" @click.stop="alert('Inner')">Inner</div>
</div>
1
2
3
4

像這樣,在 inner 區塊加上 @click.stopclick 事件就不會向外層傳遞。

試一試

# .prevent

.prevent 的作用就如同大家熟知的 event.preventDefault(),用來阻擋元素的預設行為。

<a href="#" class="btn" @click.prevent="alert('Hello!')">click me!</a>
1

當然也可以與 .stop 合用:

<a href="#" class="btn" @click.stop.prevent="alert('Hello!')">click me!</a>
1

# .capture

.capture 的作用,用來指定事件以捕獲 (capturing) 的形式來觸發:

<div class="outer" @click.capture="alert('Outer')">
  <span>Outer</span>
  <div class="inner" @click="alert('Inner')">Inner</div>
</div>
1
2
3
4

像這樣,我們在 outerclick 事件指定 .capture 修飾子, 在沒有加上 .capture 的情況點擊 inner,會先觸發 innerclick 事件再觸發 outer

而當 outerclick 事件加上 .capture 之後順序則相反,點擊 inner 時會先印出 outer 再來才是 inner

試一試

小提醒

如果不熟悉 JavaScript 事件冒泡與捕獲運作機制的朋友,可參考拙作 重新認識 JavaScript: Day 14 事件機制的原理 (opens new window) 會有更詳細的說明。

# .self

.self 的作用,只會觸發元素自己的事件行為,由子層元素傳遞來的事件則不會觸發。

舉個很常見的燈箱範例來說,當我們希望在燈箱開啟後,點擊燈箱外遮罩可以自動關閉燈箱,也許我們可以這樣做:


 






<div class="modal-mask" :style="modalStyle">
  <div class="modal-container" @click="isShow = false">
    <div class="modal-body">Hello!</div>
  </div>
</div>

<button @click="isShow = true">Click Me</button>
1
2
3
4
5
6
7
試一試

乍看之下似乎成功,但由於 modal-body 的內容區塊也在 modal-container 之下,所以當內容被點擊時,燈箱也會被關閉。

此時,若是在 modal-containerclick 事件加上 .self 修飾子,就可以排除這樣的問題:


 






<div class="modal-mask" :style="modalStyle">
  <div class="modal-container" @click.self="isShow = false">
    <div class="modal-body">Hello!</div>
  </div>
</div>

<button @click="isShow = true">Click Me</button>
1
2
3
4
5
6
7
試一試

小提醒

一個指令可以同時加入多個修飾子,而修飾子的順序會影響執行的結果

v-on:click.prevent.self 會先執行 prevent 的動作,阻擋所有的點擊行為; 而 v-on:click.self.prevent 會先執行 self ,只會阻擋該元素自己的點擊行為。

# .once

.once 修飾子的作用,讓指定的事件只會觸發一次。

<button @click.once="plus">Plus Once</button>
1
試一試

# .passive

.passive 等同於 addEventListenerpassive 屬性, 用途是告訴瀏覽器這個事件處理器會不會呼叫 event.preventDefault 來停止瀏覽器的原生行為。

<div @scroll.passive="onScroll">...</div>
1

這個屬性較常見在用來改善 scroll 事件的效能,因為以前瀏覽器要多判斷 scroll 事件會不會被 preventDefault , 加上 passive 屬性之後就直接略過這個判斷,當 passivetrue 表示此事件不會被 preventDefault

換句話說, .passive 修飾子無法與 .prevent 一起使用,因為這時 .prevent 會直接被無視。


# 鍵盤修飾子

在處理鍵盤事件時,我們常常會需要判斷使用者的按鍵來進行不同的動作,針對這類需求, Vue.js 也很貼心地提供了鍵盤修飾子來對應 event.key

試一試
  • .enter
  • .tab
  • .delete (包含 delbackspace 鍵)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
  • .ctrl
  • .alt
  • .shift
  • .meta

小提醒

.meta 在 window 對應的是視窗鍵 (⊞),在 mac 系統對應的是 command 鍵 (⌘)。

幾乎所有 event.key 在 Vue.js 都有對應的鍵盤修飾子, 不過需要注意的是像 PageDown 這類名稱要轉換成連字號 (kebab-case) .page-down 來使用。

換句話說,若你想在使用者按下 enter 鍵才觸發某個 method 時,就可以這樣做:

<!-- 當使用者在輸入框按下 enter 鍵後執行 addToMessages -->
<input type="text" 
  placeholder="輸入任意文字後按下 enter 鍵"
  v-model.trim="msg" 
  @keydown.enter="addToMessages">
1
2
3
4
5
試一試

注意

由於 keyCode 的功能已經從網頁標準中移除 (opens new window), Vue.js 自 3.0 之後也不再支援 keyCode 的用法 (參考 (opens new window)),請慎重使用。

# .exact 精確判斷

Vue.js 自 2.5 版後新增了 .exact 修飾子,用來精確判斷鍵盤的修飾子。

以上一個範例來說,當我們在 input 輸入框同時按下 Ctrl + Enter 鍵的時候也會執行 addToMessages




 

<input type="text" 
  placeholder="輸入任意文字後按下 enter 鍵"
  v-model.trim="msg" 
  @keydown.enter="addToMessages">
1
2
3
4

但是像這樣,加上了 .exact 之後就只有在按下 Enter 鍵的時候才會執行 addToMessages




 

<input type="text" 
  placeholder="輸入任意文字後按下 enter 鍵"
  v-model.trim="msg" 
  @keydown.enter.exact="addToMessages">
1
2
3
4

.exact 通常被用來作為系統按鍵的各種組合熱鍵判斷使用。

試一試

# 滑鼠修飾子

最後一個則是滑鼠事件專用的修飾子,用來提供滑鼠按鍵的點擊判斷。

  • .left
  • .right
  • .middle

# 拆解 v-model 的黑魔法

前一篇我們曾說過, v-model 並非什麼黑魔法,它本質上就是一種語法糖。 既然是語法糖,我們就可以用原有的方式來還原它。

以一個最常見的例子來說,

<input v-model="msg">
1

當我們拆解背後原理後,其實不難發現這個 v-model 實際上就等同於:

<input :value="msg" @input="msg = $event.target.value">
1

簡單來說,將 input 輸入框綁定了 value 屬性後,再針對它的 input 事件進行監聽, 當事件觸發時,便將 input 輸入框的 value 即時更新回 datamsg 屬性。

VUE與MVVM

發現了嗎? 在一開始的 Vue.js MVVM 示意圖裡頭的 ViewModel 做的事情,就是 v-model 在做的事。

而不同的表單元素在 v-model 的實作上也有些許不同,如 inputtexttextarea 元素對應的是 input 事件, 而 checkboxradio 以及 select 在背後監聽的是 change 事件。 若再加上不同修飾子之後還會有不同的效用。

所以說,如果不用 v-model,你也可以透過 v-onv-bind 改變監聽的事件來自訂想要的行為。

而除了 HTML 原生元素以外, Vue.js 也能讓開發者針對自定義元件添加各種指令,甚至是自訂事件。 這部分我們留待到第二章元件篇時還會詳細為各位說明。

Last Updated: 12/22/2020, 11:35:01 AM