
Vue.js ルーティング演習問題
基本概念まとめ 1. ルーティングの基本設定 2. ナビゲーションガード 3. 動的ルーティング 演習問題(全24問) 初級問題(6問) 基本ルーティング ナビ […]
Vue.jsにおいて、$emitは子コンポーネントから親コンポーネントへ通信するための重要なメカニズムです。前章で学んだpropsが親→子の一方通行のデータフローであったのに対し、$emitはその逆方向の通信を可能にします。
[親コンポーネント]
↑ (イベントを受け取る)
[子コンポーネント]
└── イベントを発行($emit)
この双方向のコミュニケーションパターンは、Vueのコンポーネント間通信の基礎となります。
子コンポーネントで$emit
メソッドを使用してイベントを発行します。
<template>
<button @click="notifyParent">親に通知</button>
</template>
<script>
export default {
methods: {
notifyParent() {
// 'custom-event'というイベントを発行
this.$emit('custom-event', '子からのデータ')
}
}
}
</script>
親コンポーネントではv-on
(または@
)でイベントをリッスンします。
<template>
<div>
<!-- 子コンポーネントのイベントをリッスン -->
<ChildComponent @custom-event="handleCustomEvent" />
<p>親が受け取ったデータ: {{ receivedData }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: {
ChildComponent
},
data() {
return {
receivedData: ''
}
},
methods: {
handleCustomEvent(data) {
this.receivedData = data
}
}
}
</script>
// 良い例
this.$emit('update-item', itemData)
this.$emit('form-submitted', formData)
// 悪い例
this.$emit('updateItem', itemData) // camelCaseは避ける
this.$emit('click') // ネイティブイベントと衝突する可能性
Vue 3では、コンポーネントが発行するイベントを明示的に宣言できます。
export default {
emits: ['custom-event', 'submit-form'],
methods: {
triggerEvent() {
this.$emit('custom-event', data)
}
}
}
<!-- 子コンポーネント (InputField.vue) -->
<template>
<div>
<label>{{ label }}</label>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</div>
</template>
<script>
export default {
props: ['label', 'modelValue'],
emits: ['update:modelValue']
}
</script>
<!-- 親コンポーネント -->
<template>
<div>
<InputField
label="ユーザー名"
:modelValue="username"
@update:modelValue="username = $event"
/>
<InputField
label="メールアドレス"
:modelValue="email"
@update:modelValue="email = $event"
/>
</div>
</template>
<script>
import InputField from './InputField.vue'
export default {
components: { InputField },
data() {
return {
username: '',
email: ''
}
}
}
</script>
// 子コンポーネント
methods: {
sendMultiple() {
this.$emit('multi-data', arg1, arg2, arg3)
}
}
// 親コンポーネント
<ChildComponent @multi-data="handleMultiData" />
methods: {
handleMultiData(arg1, arg2, arg3) {
// 複数の引数を処理
}
}
// 子コンポーネント
methods: {
sendObject() {
this.$emit('object-event', {
id: 123,
name: 'テストアイテム',
status: 'active'
})
}
}
// 親コンポーネント
methods: {
handleObject(data) {
console.log(data.id, data.name, data.status)
}
}
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 子コンポーネント (イベント発行側)
import { EventBus } from './eventBus'
methods: {
emitGlobal() {
EventBus.$emit('global-event', data)
}
}
// 親コンポーネント (イベント受信側)
import { EventBus } from './eventBus'
created() {
EventBus.$on('global-event', data => {
console.log('グローバルイベントを受信:', data)
})
}
// 子コンポーネント (ModalDialog.vue)
<template>
<div class="modal" v-if="visible">
<div class="modal-content">
<slot></slot>
<button @click="close">閉じる</button>
</div>
</div>
</template>
<script>
export default {
props: {
visible: Boolean
},
emits: ['close'],
methods: {
close() {
this.$emit('close')
}
}
}
</script>
<style>
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 5px;
}
</style>
// 親コンポーネント
<template>
<div>
<button @click="showModal = true">モーダルを開く</button>
<ModalDialog
:visible="showModal"
@close="showModal = false"
>
<h2>モーダルタイトル</h2>
<p>モーダルの内容がここに入ります</p>
</ModalDialog>
</div>
</template>
<script>
import ModalDialog from './ModalDialog.vue'
export default {
components: { ModalDialog },
data() {
return {
showModal: false
}
}
}
</script>
// 悪い例 - イベント名がバラバラ
this.$emit('update')
this.$emit('changed')
this.$emit('modified')
// 良い例 - 一貫した命名規則
this.$emit('update-item')
this.$emit('item-updated')
// ローカルイベント(親子間)には$emitを使用
this.$emit('local-event')
// グローバルな通信にはVuexやPiniaを使用
this.$store.commit('updateState', data)
// 悪い例 - インラインで複雑な処理
// 良い例 - メソッドに分離
methods: {
handleEvent(value) {
this.value = value
this.process(value)
this.update()
}
}
// EventBus使用時
beforeDestroy() {
EventBus.$off('event-name')
}
import { debounce } from 'lodash'
methods: {
handleInput: debounce(function(value) {
this.$emit('search', value)
}, 300)
}
// 子コンポーネント (ProductFilter.vue)
<template>
<div class="filters">
<select v-model="selectedCategory" @change="updateFilter">
<option value="all">すべて</option>
<option v-for="category in categories" :key="category">
{{ category }}
</option>
</select>
<input
v-model="priceRange"
type="range"
min="0"
max="100000"
@input="updateFilter"
>
</div>
</template>
<script>
export default {
props: ['categories'],
data() {
return {
selectedCategory: 'all',
priceRange: 0
}
},
emits: ['filter-change'],
methods: {
updateFilter() {
this.$emit('filter-change', {
category: this.selectedCategory,
maxPrice: this.priceRange
})
}
}
}
</script>
// 親コンポーネント
<template>
<div>
<ProductFilter
:categories="categories"
@filter-change="applyFilter"
/>
<ProductList :products="filteredProducts" />
</div>
</template>
<script>
import ProductFilter from './ProductFilter.vue'
import ProductList from './ProductList.vue'
export default {
components: { ProductFilter, ProductList },
data() {
return {
categories: ['電子機器', '家具', '衣類'],
products: [...], // 商品データ
currentFilter: {
category: 'all',
maxPrice: 100000
}
}
},
computed: {
filteredProducts() {
return this.products.filter(product => {
const categoryMatch =
this.currentFilter.category === 'all' ||
product.category === this.currentFilter.category
const priceMatch =
product.price <= this.currentFilter.maxPrice
return categoryMatch && priceMatch
})
}
},
methods: {
applyFilter(filter) {
this.currentFilter = filter
}
}
}
</script>
$emit
は子から親への通信手段emits
オプションで明示的に宣言propsと$emitを組み合わせることで、Vue.jsのコンポーネント間通信を効果的に実装できます。この知識を活用して、より柔軟で保守性の高いアプリケーションを構築してください。