こんにちは、かつコーチです。
前回はcomputedで算出プロパティを扱いました。
今回は、ボタンのクリックやフォームの入力など「ユーザーの操作」に反応する仕組み、v-on ディレクティブとイベントハンドラの書き方を解説します。
画面が「動く」感覚を一番実感できるパートなので、手を動かしながら読んでもらえると理解が早いです。
v-onとは?
イベントを検知するディレクティブ
v-on は、クリックやキー入力などのイベント(ユーザーの操作)を検知して、指定した処理を実行するディレクティブです。
<script setup lang="ts">
import { ref } from 'vue'
const count = ref<number>(0)
const increment = (): void => {
count.value++
}
</script>
<template>
<p>カウント:{{ count }}</p>
<button v-on:click="increment">+1</button>
</template>
ボタンをクリックするたびに increment 関数が呼ばれ、count が1ずつ増えていきます。
v-on:click という書き方に加えて、@click という省略記法もよく使われます。
<template>
<button @click="increment">+1</button>
</template>
実務では @click の形を使うケースがほとんどなので、こちらの書き方に慣れておくのがおすすめです。
メソッドとして関数を定義する場所
<script setup> の中では、通常の関数として定義した変数がそのままテンプレートから呼び出せます。
<script setup lang="ts">
import { ref } from 'vue'
const message = ref<string>('')
const showAlert = (): void => {
message.value = 'クリックされました'
}
</script>
<template>
<button @click="showAlert">押してください</button>
<p>{{ message }}</p>
</template>
Options APIの時代は methods オプションの中にまとめて書いていましたが、<script setup> ではその必要がなく、他の変数と同じ感覚で関数を書けます。
基本の書き方
イベントの引数を受け取る
イベントハンドラでは、クリックされた要素の情報などが入った event オブジェクトを受け取れます。
<script setup lang="ts">
const handleClick = (event: MouseEvent): void => {
console.log('クリック位置X:', event.clientX)
}
</script>
<template>
<button @click="handleClick">クリック位置を表示</button>
</template>
event の型は、イベントの種類によって変わります(クリックなら MouseEvent、キー入力なら KeyboardEvent など)。
TypeScriptを使う場合は、この型をきちんと指定しておくと、event.target などにアクセスするときの補完が効いて開発が楽になります。
メソッドに独自の引数を渡す
event だけでなく、自分で決めた引数を渡したい場合はどうすればいいでしょうか。
<script setup lang="ts">
interface Item {
id: number
name: string
}
const items: Item[] = [
{ id: 1, name: 'りんご' },
{ id: 2, name: 'みかん' },
]
const removeItem = (id: number): void => {
console.log(`ID ${id} を削除しました`)
}
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
<button @click="removeItem(item.id)">削除</button>
</li>
</ul>
</template>
@click="removeItem(item.id)" のように、テンプレート内で直接引数を指定して呼び出せます。
event オブジェクトも同時に受け取りたい場合は、特別な $event という変数を使います。
<template>
<button @click="removeItem(item.id, $event)">削除</button>
</template>
よくあるつまずきポイント・エラー対処
関数を()付きで呼び出してしまう
私が最初にVueを触ったとき、何も考えずに () を付けてしまい、意図と違う動きになったことがあります。
❌ Before:テンプレート読み込み時に即座に実行されてしまう
<script setup lang="ts">
const showMessage = (): void => {
alert('こんにちは')
}
</script>
<template>
<!-- ページを開いた瞬間にアラートが出てしまう -->
<button @click="showMessage()">挨拶する</button>
</template>
実はこのコード自体は動きますが、showMessage() のように括弧を付けて呼び出す形は、内部的には毎回新しい関数を生成して実行しているのと同じ扱いになります。
さらに厄介なのは、うっかり @click="showMessage" ではなく :onclick="showMessage()" のような書き方をしてしまうと、ページの描画時点で即座に実行されてしまうことです。
私はこの違いを理解しておらず、「なぜボタンを押してもいないのにアラートが出るんだろう」と数分間悩んだことがあります。
✅ After:関数名だけを渡し、呼び出しはVueに任せる
<script setup lang="ts">
const showMessage = (): void => {
alert('こんにちは')
}
</script>
<template>
<!-- 関数名(参照)だけを渡す -->
<button @click="showMessage">挨拶する</button>
</template>
引数を渡す必要がない場合は、@click="関数名" のように括弧を付けずに関数の参照だけを渡すのが基本形です。
引数を渡したいときだけ @click="関数名(引数)" の形にする、と覚えておくと迷いません。
イベント修飾子を知らずに自前で制御してしまう
フォームの送信でページがリロードされてしまう、というのも初心者がよく踏むつまずきです。
❌ Before:event.preventDefault()を毎回自分で書く
<script setup lang="ts">
const handleSubmit = (event: Event): void => {
event.preventDefault() // 忘れると画面がリロードされてしまう
console.log('送信処理')
}
</script>
<template>
<form @submit="handleSubmit">
<button type="submit">送信</button>
</form>
</template>
このコード自体は正しく動きますが、event.preventDefault() を書き忘れるとフォーム送信のたびにページがリロードされてしまいます。
私も最初のうちは、この一行を書き忘れて「送信ボタンを押すと入力内容が全部消える」という不具合を何度も作ってしまいました。
✅ After:イベント修飾子.preventで簡潔に制御する
<script setup lang="ts">
const handleSubmit = (): void => {
console.log('送信処理')
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<button type="submit">送信</button>
</form>
</template>
@submit.prevent のようにイベント修飾子を付けると、event.preventDefault() を書かなくてもVue側が自動的にデフォルト動作をキャンセルしてくれます。
同様に、クリックイベントの伝播を止める @click.stop、Enterキーだけに反応する @keyup.enter など、よく使う修飾子はセットで覚えておくとコードがすっきりします。
| 修飾子 | 意味 |
|---|---|
.prevent | デフォルト動作をキャンセル(フォーム送信のリロード防止など) |
.stop | イベントの親要素への伝播を止める |
.once | 一度だけ実行する |
.enter | Enterキー押下時のみ実行(@keyup.enter) |
応用・一歩先の使い方
複数のイベントを1つの要素にまとめる
1つの要素に複数のイベントを設定したい場合は、そのまま並べて書くだけです。
<script setup lang="ts">
import { ref } from 'vue'
const isHovered = ref<boolean>(false)
const handleEnter = (): void => {
isHovered.value = true
}
const handleLeave = (): void => {
isHovered.value = false
}
</script>
<template>
<div
@mouseenter="handleEnter"
@mouseleave="handleLeave"
:class="{ hovered: isHovered }"
>
マウスを乗せてみてください
</div>
</template>
@mouseenter と @mouseleave を組み合わせることで、ホバー中だけスタイルを切り替えるような表現も簡単に実現できます。
イベントとcomputedやrefを組み合わせることで、静的なページでは実現できなかった「操作に応じて変化するUI」が作れるようになります。
まとめ
この記事のポイント
v-on(省略形@)でクリックなどのイベントを検知し、関数を実行できる<script setup>では通常の関数をそのままイベントハンドラとして使える- 引数不要なら
@click="関数名"、引数を渡すなら@click="関数名(引数)"と使い分ける .preventや.stopなどのイベント修飾子で、デフォルト動作の制御を簡潔に書ける- 複数のイベントは1つの要素に並べて指定できる
次に読むべき記事
次回は、属性やプロパティを動的に切り替える v-bind の使い方を解説します。
→ 次の記事:v-bindで属性・プロパティを動的にバインドする