タイトル: クリック時のイベント処理(on:click)
SEOタイトル: Vue.js @click / v-on:click 完全ガイド(修飾子・$event・Composition API・$emit)
| この記事の要点 |
|
基本: @click と v-on:click
Vue.js でクリックイベントを扱う最も基本的な書き方は v-on:click ディレクティブで、@ で省略できます。
<template>
<!-- 省略形 (推奨) -->
<button @click="increment">+1</button>
<!-- 正式形 -->
<button v-on:click="increment">+1</button>
<!-- インライン式 -->
<button @click="count++">+1</button>
<!-- 引数を渡す -->
<button @click="addItem('apple')">Apple</button>
<!-- $event とともに渡す -->
<button @click="addItem('apple', $event)">Apple</button>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => { count.value++; };
const addItem = (name, event) => {
console.log(name, event.target);
};
</script>
$event: ネイティブイベントオブジェクト
メソッドの引数に $event を渡すと、DOM の MouseEvent をそのまま受け取れます。
<template>
<button @click="onClick($event)">クリック</button>
<button @click="onClick">クリック (自動)</button>
</template>
<script setup>
const onClick = (e) => {
console.log(e.target); // クリックされた要素
console.log(e.clientX, e.clientY); // 座標
console.log(e.shiftKey, e.ctrlKey); // 修飾キー
e.preventDefault(); // デフォルト動作キャンセル
e.stopPropagation(); // バブリング停止
};
</script>
引数を書かずに @click="onClick" としても、第一引数に自動的に MouseEvent が渡されます。
イベント修飾子(Modifiers)
頻出のイベント処理を 修飾子で 1 行に書けます。e.preventDefault() 等を JS 側に書かずに済みます。
| 修飾子 | 動作 | 等価な JS |
|---|---|---|
.stop | イベント伝播を止める | e.stopPropagation() |
.prevent | デフォルト動作をキャンセル | e.preventDefault() |
.capture | キャプチャフェーズで処理 | addEventListener の第 3 引数 true |
.self | 自身が target のときだけ発火 | if (e.target === e.currentTarget) |
.once | 一度だけ発火 | addEventListener の {once:true} |
.passive | パッシブリスナー(スクロール最適化) | addEventListener の {passive:true} |
<!-- バブリング停止 -->
<div @click="onOuter">
<button @click.stop="onInner">押しても onOuter は呼ばれない</button>
</div>
<!-- ページ遷移を止めて Ajax 送信 -->
<form @submit.prevent="onSubmit">
<button>送信</button>
</form>
<!-- 一度だけ -->
<button @click.once="onceOnly">一度だけ反応</button>
<!-- 修飾子は連結可 -->
<a @click.stop.prevent="onLink" href="/foo">独自処理</a>
<!-- 自身がクリックされたときだけ -->
<div @click.self="onBackdrop" class="modal-backdrop">
<div class="modal-content">子要素クリックは無視される</div>
</div>
マウスボタン・キー修飾子
<!-- 左クリックのみ -->
<button @click.left="onLeft">左クリック</button>
<!-- 右クリックのみ -->
<button @click.right="onRight">右クリック</button>
<!-- 中ボタンクリック -->
<button @click.middle="onMiddle">中ボタン</button>
<!-- Ctrl + クリック -->
<button @click.ctrl="onCtrlClick">Ctrl+Click</button>
<!-- Shift + クリック(他のキーは無視) -->
<button @click.shift.exact="onShiftOnly">Shift+Click のみ</button>
Composition API でのクリック処理
<script setup>
import { ref, computed } from 'vue';
const count = ref(0);
const items = ref([]);
// シンプルな関数
const increment = () => count.value++;
// 引数あり
const remove = (id) => {
items.value = items.value.filter(i => i.id !== id);
};
// async も使える
const submit = async () => {
const res = await fetch('/api/save', { method: 'POST' });
if (!res.ok) console.error('失敗');
};
</script>
<template>
<button @click="increment">{{ count }}</button>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
<button @click="remove(item.id)">削除</button>
</li>
</ul>
<button @click="submit">保存</button>
</template>
子コンポーネントから親へ: $emit
子コンポーネントでクリックが起きたとき、親に通知するには $emit(Composition API では defineEmits)を使います。
<!-- 子: MyButton.vue -->
<script setup>
const emit = defineEmits(['click', 'longpress']);
const onNativeClick = (e) => {
emit('click', { x: e.clientX, y: e.clientY });
};
</script>
<template>
<button @click="onNativeClick"><slot /></button>
</template>
<!-- 親: Parent.vue -->
<script setup>
const handleChildClick = (payload) => {
console.log('子からの payload:', payload);
};
</script>
<template>
<MyButton @click="handleChildClick">押す</MyButton>
</template>
v-on のオブジェクト構文(複数イベント)
<template>
<!-- 複数のイベントをまとめて -->
<input v-on="{
click: onClick,
focus: onFocus,
blur: onBlur,
keydown: onKey,
}" />
</template>
React onClick との比較
| 項目 | Vue.js | React |
|---|---|---|
| 記法 | @click="fn" | onClick={fn} |
| 引数渡し | @click="fn(id)" | onClick={() => fn(id)} |
| イベント取得 | $event または第一引数 | 第一引数 SyntheticEvent |
| preventDefault | @click.prevent | e.preventDefault() 手動 |
| stopPropagation | @click.stop | e.stopPropagation() 手動 |
| カスタムイベント | $emit + defineEmits | props として渡す |
よくあるトラブル
@click="fn()"と書いてレンダリング時に即実行されてしまう → 引数が無いなら@click="fn"に。引数があるなら@click="fn(arg)"のまま OK(テンプレ式は遅延評価される)- v-for 内でクリック先が間違う →
:keyが無い / 重複している。一意キーを付ける - 子コンポーネントの @click が効かない → ルート要素が単一でない場合、attrs 継承されない。
defineEmits(['click'])+@click="$emit('click', $event)"で明示 - ボタン連打で多重送信 →
.once修飾子、またはdisabled制御
FAQ
Q: Vue 2 と Vue 3 で書き方は変わる?
A: 基本構文は同じ(@click、修飾子、$emit)。Vue 3 は Composition API(<script setup>)と defineEmits が追加されたぶん、より明示的になりました。
Q: 動的なイベント名は使える?
A: 使えます。@[eventName]="handler" のように動的引数を指定できます。Vue 2.6+ / Vue 3。
Q: addEventListener との違いは?
A: @click は内部で addEventListener を呼んでいます。コンポーネントが unmount されるとリスナーも自動解除されるので、手動 cleanup 不要。