今天來聊聊 Vue 中事件的寫法。
如果你有看過怎麼在 HTML 寫事件,那你對於 Vue 提供的事件語法應該會很熟悉。
<div onclick="functionToExecute()"></div>
基礎語法結構會是: v-on:[事件名稱]
,例如 v-on:click="func()"
、 v-on:keydown="func()"
。
嫌 v-on
四個字元太多字,也可以把這四個字省略成 @
。 例如 @click="func()"
、@keydown="func()"
在模板上看起來會長這樣:
<button @click="plus()">+</button>
說到事件,不得不提到事件的參數,在 方法 的篇章已經有聊到 v-on
指令常會搭配 「方法」一起使用。
當時沒有講到的是, v-on
綁定的 method 支援一個特殊的參數 $event
,可以讓我們取得事件的 event 物件。
這個物件在傳統 JS 來說可能會寫個 e
來接這個參數,像這樣:
const $btn = document.getElementById("btn");
$btn.addEventListener("click", function (e) {
console.log(e);
});
而如果是 v-on
指令,則可以這樣寫:
<div id="app">
<!-- 一定要是 $event 不能是其他值 -->
<a href="https://google.com.tw" @click="plus($event)">{{data}}</a>
</div>
Vue.createApp({
setup() {
const data = Vue.ref(0);
const plus = (e) => {
//模板有寫 $event 這裡就能抓到了
e.preventDefault();
data.value++;
};
return { data, plus };
},
}).mount("#app");
不過如果方法中需要帶其他參數,就要記得,模板寫的參數位置,跟方法中的參數位置必須一樣:
<div id="app">
<!-- 因為在實體中寫的參數,第一個是接「事件參數」,第二個是某個資料值,所以兩者順序不能換 -->
<a href="https://google.com.tw" @click="plus($event , 5)">{{data}}</a>
</div>
Vue.createApp({
setup() {
const data = Vue.ref(0);
const plus = (e, value) => {
//模板有寫 $event 這裡就能抓到了
e.preventDefault();
data.value = value;
};
return { data, plus };
},
}).mount("#app");
針對一些特殊的事件操作,Vue 支援一些特殊的修飾子。
.stop
.prevent
.capture
.self
.once
一個一個來介紹
他就是 .stopPropagation()
,阻擋事件冒泡用的。
我們都像是這種 HTML 結構,如果在 .inner
綁點擊事件,點了他之後,同時也會讓 .outer
也觸發點擊事件。
一般我們就會使用 e.stopPropagation()
來阻擋事件冒泡。
<div class="outer">
這是外面
<div class="inner">這是裡面</div>
</div>
因為很常用,所以 Vue 提供了 .stop
語法糖讓我們使用,用法就是直接在 event 名稱接 .stop
即可。
<div id="app">
<div class="outer" @click="console.log('outer')">
這是外面
<div class="inner" @click.self="console.log('inner')">這是裡面</div>
</div>
</div>
如果你懂 JS ,可能也不用多說什麼了,這個修飾子就是 e.preventDefault()
,阻擋預設行為用的。
例如上面在講 $event 的範例,methods 接了 a 標籤的點擊事件後,做了 e.preventDefault()
的行為。
事實上在 Vue 中可以直接這樣寫:
<a href="https://google.com.tw" @click.prevent="console.log('hello!!!')"
>點我啊</a
>
而且 Vue 的事件就像 jQuery 語法一樣,是可以接續著寫下去的:
<a href="https://google.com.tw" @click.stop.prevent="console.log('hello!!!')"
>點我啊</a
>
我們再把這個結構請出來用:
<div class="outer">
這是外面
<div class="inner">這是裡面</div>
</div>
一般來說,如果沒有做任何設定,點擊 inner 時,會先觸發 inner 的 click ,才會觸發 outer 的 click 事件。
這是預設的 JS 事件冒泡的機制。
不過如果我們在「外層」加一個 .capture,那整個操作行為就會反過來,變成先觸發 outer ,才觸發 inner
。
<div class="outer" @click.capture="console.log("hello!")">
這是外面
<div class="inner" @click="console.log('裡面!')">
這是裡面
</div>
</div>
self
中文是 :自己。
所以當事件加上這個修飾子,就會限定只點自己才有效。
剛剛在 .stop
的地方有講到事件冒泡,點擊內層結構也會觸發外層結構的事件。
以常見的「燈箱效果」來說,如果這個結構是一個燈箱效果,外面那層是整個黑幕,裡面才是實際的燈箱,當燈箱跳出來的時候,我們希望點擊黑幕才觸發關閉事件,點到內容不做任何事,那我們就可以直接在外面那層加上 .self
<div class="outer">
這是外面
<div class="inner">這是裡面</div>
</div>
好啦...我知道我說得很清楚,你卻聽得很模糊,直接進去看程式,試試看把 .self
拿掉,打開燈箱,然後點點看「這是裡面」,看會有什麼變化吧。
可能有人會把 .self 跟 .stop 搞混,會以為 在內層設定 .self 可以阻止冒泡。
這裡必須強調 .self 不是拿來阻止冒泡行為的!!!!
這個就跟 v-once
的概念很像, v-once
是資料綁到 模板 之後,資料怎麼更動後都不會再重新渲染。.once
修飾子,就是讓事件只觸發一次,然後就不會再有然後了。
.once
跟 v-once
不太一樣的地方是,之前講 v-once
的時候,示範畫面沒有異動,但資料還是有被異動。
不過 .once
控制的是「事件」,換言之是讓那個 function 只觸發一次,所以如果裡面有改動資料,就也只會改動那一次,之後怎麼執行事件,事件的函式就是不會再執行。
<div id="app">
<button @click.once="plus()">{{count}}</button>
</div>
Vue.createApp({
setup() {
const count = Vue.ref(0);
const plus = () => {
count.value++;
console.log(count.value);
};
return { count, plus };
},
}).mount("#app");
除了通用修飾子,針對鍵盤, Vue 也提供一些不錯用的修飾子
例如執行 @keydown
事件時,只想讓 enter 鍵才執行某個行為,則可以寫
<div @keydown.enter="console.log('enter')"></div>
其他常見的還有這些:
.enter
.tab
.delete
.esc
.space
.ctrl
.shift