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