seansie's blog

#4 事件處理 30天學會 vue系列文 | seansie blog

4 v-on事件處理&methods元件邏輯函數

在第零章我提到,網頁是一種事件驅動的應用程式,因此, vuejs 對事件處理這方面下了不少功夫,也提供了許多開箱即用的好用功能。

v-on介紹

如果你是照順序看的,恐怕是第一次看到 v開頭的語法吧,這是 vuejs 中提供的一種方便功能,術語叫做 V指令, v-on 是裡面的其中一種,甚至還可以自訂自己的V指令

v-on 就是裡面在處理事件的,常常會跟按紐一起用,例如

<button v-on:click="myfuncttion()">GO</button>

語法介紹

<標籤 v-on:事件名稱="執行的js陳述"></標籤>

對了,這種在標籤中有 foo="bar" 這種格式的東西,叫做屬性 (property),爾後會用術語來描述。

這個可以簡單離解成該標籤的性質(形容詞)。

因此所有 V指令都是一種特殊的屬性,用來微調該標籤的行為。

v-on 因為太常用了,還有一種簡寫的方法如下

<標籤 @事件名稱="要執行的js陳述"></標籤>

直接在事件名稱前面加上 @ 的屬性就會被自動轉換成 v-on:事件名稱

舉個最簡單的,我們可以在按鈕物件上監聽 按下 這個事件,而按下這個事件在vue中叫做 click

因此可以寫成

<button @click="alert('按鈕被按下了')">點我</button>

對了雙引號中的 "要執行的js陳述" ,如果js陳述式中有雙引號,請把它改成單引號,以免跟html屬性外面的雙引號相衝,在js中單引號跟雙引號幾乎相同。

然後執行後,如果按下該按紐,就會彈出一個對話方塊,上面寫著 按鈕被按下了


咦! 怎麼沒有? 還出現了一堆錯誤! 節錄如下

vue.global.js:2479 Uncaught TypeError: alert is not a function
    at onClick (eval at compileToFunction (vue.global.js:17918:20), <anonymous>:15:29)
    at callWithErrorHandling (vue.global.js:2410:21)
    at callWithAsyncErrorHandling (vue.global.js:2417:19)
    at HTMLButtonElement.invoker (vue.global.js:11207:7)

翻譯如下, alert 不是函數,這時你可能會想說, alert() 就是正派的函數阿,是被定義在MDN docs (前端開發最權威的免費使用手冊手冊之一)(連結如下),怎麼會不認得呢?

https://developer.mozilla.org/en-US/docs/Web/API/Window/alert

原因就出在 @click="myfunc()" 執行的時候,它的作用域(可以理解成社交圈),就是在vue的元件內,換句話說,他只認得 export default {} 大括號中裡面的物件,如果是他之外的他是認不出來的,因此就沒辦法執行(因為不認識)

那怎麼辦呢? 可以透過methods{} !,也就是下一節的內容

對了,題外話,剛剛提到只能操作元件內的物件,那如果你會舉一反三,肯定會想到能不能操作 data() ,當然可以,以下就來示範

<button @click="msg+='@'">按鈕</button>

export default {
		data(){
			return{
				msg: "Vuejs"
			}
		},
		methods:{ 省略}
	}

這樣就是操作 data()裡面資源的範例,如上所示,透過 msg+='@' ,在每次按鈕按下時就會在 msg 後面加上一個 @ ,如下所示

  • 0次 msg="Vuejs"
  • 1次 msg=Vuejs@
  • 2次 msg=Vuejs@@

以此類推

methods:

在元件中,不僅可以定義靜態的物件在 data() 的回傳值 return 中,有點像名詞,也可以定義動態的函數在 methods 物件中,有點像動詞,定義如下

methods:{
	屬性名稱1 :function 函數名稱{
		函數本體
	}  

例如可以依樣畫葫蘆,透過 methods 修正剛剛發生的 alert() 錯誤

// <template>
		<button @click="alertProp()">按鈕</button>  
// 元件本體
data(){
			return{
				msg: "Vuejs"
			}
		},
		methods:{
			alertProp : function alertName(){  // prop表示屬性簡寫,命名而已,不一定要跟我一樣
				alert()
			}
		}

這樣就可以順利執行了!

而因為當我們使用函數的時候,會透過屬姓名稱(例如 alertProp來呼叫),基本上不會用到函數名稱,所以基本上可以把他們兩個寫成同名,而 vuejs 提供了一種方法來簡化,剛剛的函數可以進一步簡化為

// 在元件本體中
methods:{
			alertName(){
				alert()
			}
		}

觸發事件

當然,事件百百種,除了 click 事件,Vue 還有五花八門的其他事件類型任君挑選,如下:

  • dblclick:快速點擊兩下,常用於按鈕。

如果是用在段落 (例如:) 可能不明顯,因為點擊兩次會選取文字,會對使用者造成干擾。 此外,若混用 dblclick 跟 click 事件,要注意同步狀態,可能會發生不可預期的錯誤,因為 click 可能會被觸發兩次。

  • mousedown:滑鼠按鈕按下時觸發。
  • mouseup:滑鼠按鈕放開時觸發。

修飾子

Vue 提供了一些進階選項,來更好地微調事件的觸發規則:

(以下為常見的修飾子,你可以繼續補充)

  • .stop:阻止事件冒泡 (stopPropagation)。

在 HTML DOM 中,當一個元素上的事件被觸發時(例如點擊一個按鈕),這個事件不僅僅只發生在這個元素身上。事件會像泡泡一樣,從觸發事件的那個元素開始,一層一層地往上傳遞到它的父元素、祖父元素,一直到最頂層的 document,這個過程就稱為「事件冒泡」。

  • .prevent:阻止事件的預設行為 (preventDefault)。
  • .capture:使用事件捕獲模式。
  • .self:只當事件是由事件綁定元素本身觸發時才觸發處理函數。
  • .once:事件只觸發一次。
  • .passive:告訴瀏覽器這個事件處理器不會調用 preventDefault,有助於提高滾動性能 (尤其在移動設備上)。
  • .native: 監聽組件根元素的原生事件。
  • .left: (v2.2.0) 只在滑鼠左鍵點擊時觸發。
  • .right: (v2.2.0) 只在滑鼠右鍵點擊時觸發。
  • .middle: (v2.2.0) 只在滑鼠中鍵點擊時觸發。
  • .exact: (v2.5.0) 允許控制觸發事件所需的确切系統修飾符組合。

修飾子

vue 提供了一些進階選項,來更好的微調事件的觸發規則

牛刀小試

題目:建立一個簡易的文字記錄器

請使用 Vue.js 3 (Composition API) 建立一個簡單的文字記錄器。使用者可以在一個輸入框中輸入文字,點擊按鈕或按下 Enter 鍵後,將輸入的文字連同當下的日期時間(格式為 YYYY/MM/DD HH:mm)添加到一個顯示區域。

功能要求:

  1. 輸入框: 提供一個文字輸入框 (<input type="text">) 讓使用者輸入內容。
  2. 新增按鈕: 提供一個按鈕,點擊後觸發新增記錄的功能。
  3. Enter 鍵觸發: 在輸入框中按下 Enter 鍵,也能觸發新增記錄的功能。
  4. 顯示區域: 使用 <pre> 標籤來顯示記錄內容。確保記錄內容能夠保留換行。
  5. 日期時間格式: 每筆記錄的前面要加上當下的日期時間,格式為 YYYY/MM/DD HH:mm(年份四位數、月份兩位數、日期兩位數、小時兩位數、分鐘兩位數,不足兩位數時前面補零)。
  6. 輸入檢查: 如果輸入框內容為空或僅包含空格,則點擊按鈕或按下 Enter 鍵時不執行任何操作。
  7. 新增後清空: 成功新增一筆記錄後,清空輸入框的內容。

範例輸出:

假設使用者在 2024 年 7 月 4 日 15:30 輸入了 “Hello, Vue!",然後點擊了新增按鈕,<pre> 標籤內的顯示內容應該如下:

2024/07/04 15:30 user input "Hello, Vue!"

如果使用者接著又輸入"測試換行\n成功” <pre> 標籤內的顯示內容應該如下:

2024/07/04 15:30 user input "Hello, Vue!" 2024/07/04 15:31 user input "測試換行 成功"

提示:

  • 可以使用 v-model 進行雙向綁定。
  • 可以使用 @click 監聽按鈕點擊事件。
  • 可以使用 @keyup.enter 監聽 Enter 鍵事件。
  • 可以使用 JavaScript 的 Date 物件來獲取當前時間。
  • 可以使用 padStart 方法來補零。
  • <pre> 標籤的 CSS 樣式 white-space: pre-line; 可以保留換行。

解答

<template>
  <div id="app">
    <pre>{{ logContent }}</pre>
    <div class="input-area">
      <input type="text" v-model="userInput" @keyup.enter="addLog">
      <button @click="addLog">新增記錄</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      logContent: '',
      userInput: ''
    };
  },
  methods: {
    addLog() {
      if (this.userInput.trim() === '') {
        return;
      }

      const now = new Date();
      const year = now.getFullYear();
      const month = String(now.getMonth() + 1).padStart(2, '0');
      const day = String(now.getDate()).padStart(2, '0');
      const hours = String(now.getHours()).padStart(2, '0');
      const minutes = String(now.getMinutes()).padStart(2, '0');

      const formattedDate = `${year}/${month}/${day} ${hours}:${minutes}`;
      this.logContent += `${formattedDate} user input "${this.userInput}"\n`;

      this.userInput = '';
    }
  }
};
</script>

<style scoped>
pre {
  border: 1px solid #ccc;
  padding: 10px;
  margin-bottom: 10px;
  white-space: pre-line;
}
.input-area{
  display: flex;
}
</style>