ボタンのクリックやフォームの送信、キー入力など、ユーザーの操作に反応するときに使うのがv-onです。この記事では、Vue 3(<script setup>)を前提に、基本の書き方から、イベント修飾子、コンポーネントのイベントまでを動くコードで整理します。
基本の書き方
v-on:clickの省略形が@clickです。右辺には、メソッド名を書く方法と、式をそのまま書く方法があります。まずはメソッドを呼び出す形です。
<script setup>
import { ref } from 'vue'
const count = ref(0)
function handleClick() {
count.value++
}
</script>
<template>
<!-- @click は v-on:click の省略形 -->
<button @click="handleClick">クリック</button>
<p>{{ count }}</p>
</template>
メソッドハンドラとインラインハンドラ
Vueは、v-onの値を見て、メソッドハンドラかインラインハンドラかを自動で判別します。
- メソッドハンドラ:値が
foo、foo.barのような識別子やプロパティのパスのとき - インラインハンドラ:
foo()やcount++のように式になっているとき
単純な操作ならインラインで済みます。処理が長くなる場合や、再利用したい場合はメソッドに切り出します。
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<!-- count++ はインラインハンドラ(そのまま式を書ける) -->
<button @click="count++">カウントアップ</button>
<p>{{ count }}</p>
</template>
イベントオブジェクトと引数を渡す
メソッドハンドラには、ネイティブのイベントオブジェクトが第1引数として自動で渡されます。インラインハンドラで引数と一緒にイベントオブジェクトも使いたいときは、$eventを渡します。アロー関数でも書けます。
<script setup>
function warn(message, event) {
if (event) event.preventDefault()
alert(message)
}
</script>
<template>
<!-- $event でネイティブのイベントオブジェクトを渡す -->
<button @click="warn('まだ送信できません', $event)">送信</button>
<!-- アロー関数でも同じ -->
<button @click="(event) => warn('まだ送信できません', event)">送信</button>
</template>
イベント修飾子
ハンドラの中でevent.preventDefault()などを書く代わりに、修飾子を付けて宣言的に書けます。フォームの送信を止める例が代表的です。
<script setup>
import { ref } from 'vue'
const message = ref('')
function handleSubmit() {
alert('送信: ' + message.value)
}
</script>
<template>
<!-- ページ遷移(デフォルトのsubmit)を止めて handleSubmit だけ実行 -->
<form @submit.prevent="handleSubmit">
<input type="text" v-model="message">
<button type="submit">送信</button>
</form>
</template>
使える修飾子は次の6つです。
<!-- 伝播を止める --> <a @click.stop="doThis"></a> <!-- 要素自身がイベントの発生元のときだけ実行 --> <div @click.self="doThat">...</div> <!-- キャプチャーモードで受ける --> <div @click.capture="doThis">...</div> <!-- 最大1回だけ実行 --> <a @click.once="doThis"></a> <!-- スクロール性能の改善(preventDefaultしないことを宣言) --> <div @scroll.passive="onScroll">...</div> <!-- 修飾子は連結できる(順序に意味がある) --> <form @submit.prevent.stop="onSubmit"></form>
- 順序に意味があります。
@click.prevent.selfは、要素自身と子要素のクリックのデフォルト動作を止めます。@click.self.preventは、要素自身のクリックのデフォルト動作だけを止めます。 .passiveと.preventは併用できません。.passiveは「デフォルト動作を止めない」とブラウザに伝えるものなので、併用するとブラウザが警告を出します。
キー修飾子とマウスボタン修飾子
キー入力では、キー名を修飾子として付けると、そのキーのときだけハンドラが動きます。.enter、.tab、.delete(DeleteとBackspaceの両方)、.esc、.space、矢印キーの.upなどがあります。.ctrl、.alt、.shift、.metaは、キーを押しながらの操作に使います。.exactを付けると、指定したキーの組み合わせだけのときに限定できます。マウスボタンは.left、.right、.middleです。
<!-- Enterキーを押したときだけ実行 --> <input @keyup.enter="submit"> <!-- Escで閉じる --> <input @keyup.esc="close"> <!-- Ctrl + クリック --> <button @click.ctrl="onCtrlClick">A</button> <!-- Ctrlだけを押しているときのみ(他のキーとの組み合わせは除外) --> <button @click.ctrl.exact="onCtrlClick">A</button> <!-- 右クリック --> <div @click.right="onRightClick">...</div>
コンポーネントのイベントを購読する
子コンポーネントのイベントも、同じ@で購読できます。子側はdefineEmitsで発行するイベントを宣言し、emitで通知します。親は@submitのように受け取ります。
<!-- 子コンポーネント(TodoForm.vue) -->
<script setup>
const emit = defineEmits(['submit'])
function onSubmit() {
emit('submit', { title: '買い物' })
}
</script>
<template>
<button @click="onSubmit">追加</button>
</template>
<!-- 親コンポーネント -->
<script setup>
import TodoForm from './TodoForm.vue'
function addTodo(todo) {
console.log(todo.title)
}
</script>
<template>
<TodoForm @submit="addTodo" />
</template>
Vue 3では、Vue 2の.native修飾子は廃止されました。子でemitsに宣言していないイベントは、子のルート要素のネイティブリスナーとして扱われます。
関連するディレクティブ
- 属性へのバインディングは属性バインディングの使い方
- 入力値の双方向バインディングは双方向データバインディングでフォームの入力値を反映させる方法、仕組みはv-modelの仕組みを完全理解
- 条件による表示切り替えはv-ifで条件分岐させる方法
- リスト表示はv-forで配列やオブジェクトをループして表示する方法
- 親子コンポーネントの連携はprovide/injectの仕組みと応用例
よくある質問(FAQ)
@clickはv-on:clickの省略形で、同じ意味です。warn('…', $event))では、$eventで渡します。.prevent.selfと.self.preventでは、デフォルト動作を止める範囲が変わります。.passiveは「デフォルト動作を止めない」と宣言するもので、併用するとブラウザが警告を出します。まとめ
v-onは@で書き、単純な操作はインライン、複雑な処理はメソッドに分けるのが基本です。フォーム送信には.prevent、キー入力には.enterのような修飾子を使うと、ハンドラの中をすっきり保てます。コンポーネント間では、defineEmitsで宣言したイベントを@で購読します。
