【Vue.js】v-onでDOMイベントを購読する方法

【Vue.js】v-onでDOMイベントを購読する方法 Vue.js

ボタンのクリックやフォームの送信、キー入力など、ユーザーの操作に反応するときに使うのがv-onです。この記事では、Vue 3(<script setup>)を前提に、基本の書き方から、イベント修飾子、コンポーネントのイベントまでを動くコードで整理します。

スポンサーリンク

基本の書き方

v-on:clickの省略形が@clickです。右辺には、メソッド名を書く方法と、式をそのまま書く方法があります。まずはメソッドを呼び出す形です。

Vue:メソッドハンドラ
<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++のように式になっているとき

単純な操作ならインラインで済みます。処理が長くなる場合や、再利用したい場合はメソッドに切り出します。

Vue:インラインハンドラ
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <!-- count++ はインラインハンドラ(そのまま式を書ける) -->
  <button @click="count++">カウントアップ</button>
  <p>{{ count }}</p>
</template>

イベントオブジェクトと引数を渡す

メソッドハンドラには、ネイティブのイベントオブジェクトが第1引数として自動で渡されます。インラインハンドラで引数と一緒にイベントオブジェクトも使いたいときは、$eventを渡します。アロー関数でも書けます。

Vue:$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()などを書く代わりに、修飾子を付けて宣言的に書けます。フォームの送信を止める例が代表的です。

Vue:.preventでデフォルトの送信を止める
<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つです。

Vue:イベント修飾子の一覧
<!-- 伝播を止める -->
<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です。

Vue:キー・システム・マウスボタンの修飾子
<!-- 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のように受け取ります。

Vue:子コンポーネントでイベントを発行する
<!-- 子コンポーネント(TodoForm.vue) -->
<script setup>
const emit = defineEmits(['submit'])

function onSubmit() {
  emit('submit', { title: '買い物' })
}
</script>

<template>
  <button @click="onSubmit">追加</button>
</template>
Vue:親コンポーネントで受け取る
<!-- 親コンポーネント -->
<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に宣言していないイベントは、子のルート要素のネイティブリスナーとして扱われます。

関連するディレクティブ

よくある質問(FAQ)

Qv-on:clickと@clickの違いは?
A違いはありません。@clickはv-on:clickの省略形で、同じ意味です。
Qメソッドの引数にイベントオブジェクトを使うには?
Aメソッド名だけを書いたときは、第1引数に自動で渡されます。引数を付けて呼び出す形(warn('…', $event))では、$eventで渡します。
Q修飾子を複数付けたとき、順序は関係ありますか?
Aあります。修飾子は並べた順にコードが生成されます。たとえば.prevent.selfと.self.preventでは、デフォルト動作を止める範囲が変わります。
Q.passiveと.preventを一緒に使えますか?
A使えません。.passiveは「デフォルト動作を止めない」と宣言するもので、併用するとブラウザが警告を出します。

まとめ

v-onは@で書き、単純な操作はインライン、複雑な処理はメソッドに分けるのが基本です。フォーム送信には.prevent、キー入力には.enterのような修飾子を使うと、ハンドラの中をすっきり保てます。コンポーネント間では、defineEmitsで宣言したイベントを@で購読します。