🔮

Vue.jsで孫 → 親コンポーネントへの値の受け渡しを楽に行う方法

2021/11/12に公開

サマリ

Vue.jsで孫 から 親のコンポーネントへ値を渡す際、$emitを使いますが、中間のコンポーネントが増えてくるとその分emitを記述しないといけません。

この中間のコンポーネントでのemitの記述を減らすのに$listenersが使えますので、その紹介をします。

$emit$listenersの比較

$emitを使った場合

// Parent.vue
<template>
	<Child @click="onClick" />
</template>

<script>
export default {
	components: {
		Child,
	},
	methods: {
		onClick(value) {
			alert(value);
		},
	},
}
<script>
// Child.vue
<template>
	<GrandChild @click="onClick" />
</template>

<script>
export default {
	components: {
		GrandChild
	},
	methods: {
		onClick(value) {
			this.$emit('click', value);
		}
	},
}
<script>
// GrandChild.vue
<template>
	<button @click="onClick">Hello World</button>
</template>

<script>
export default {
	methods: {
		onClick(event) {
			this.$emit('click', event.target.textContent);
		},
	},
}
<script>

※ 上記のコードはサンプルのため実際の案件コードとは異なります。

今回はバケツリレーの例のためご了承ください。

こんなのがさらに何階層も続くと、中間のコンポーネントを作成する際に以下の状態になってしまいます。

「また$emitを書かないといけないのか、、」

「あー、このコンポーネントにも$emit書かなきゃ、、、」

しまいには記載漏れしてる子コンポーネントがないか心配になってしまいます、。

(;・∀・)ダ、ダイジョウブ…?

そんな時に$listenersが使えます!

$listenersを使った場合

// Parent.vue
<template>
	<Child @click="onClick" />
</template>

<script>
export default {
	components: {
		Child,
	},
	methods: {
		onClick(value) {
			alert(value);
		},
	},
}
<script>
// Child.vue
<template>
	<GrandChild @click="$listeners['click']" />
</template>
// GrandChild.vue
<template>
	<button @click="onClick">Hello World</button>
</template>

<script>
export default {
	methods: {
		onClick(event) {
			this.$emit('click', event.target.textContent);
		},
	},
}
<script>

中間のコンポーネントでemitさせる際の記述が減りましたね!

どうですか!

楽ですよね!?

書き方の説明

@clickのclickの部分は、子コンポーネントから$emitされたイベント名を記載し、

$listeners['']の中に記載するのは親で受け取りたいイベント名を記載します

<template>
	<GrandChild @click="$listeners['click']" />
</template>

まとめ

バケツリレーの際、中間に挟まれてるコンポーネントでは積極的に$listenersを使って、記述量を減らしていきましょう!

※ 実は$listenersはVue3で削除されてます、、!

※ ただ、Vue3はIE未対応のため、2022年の6月まではこのwikiは役に立つ想定です、、

おまけ

また、この$listenersの挙動の面白いところがありまして、子コンポーネントにv-on="$listeners"と記載すると、子のイベントリスナを親側で直接参照できるんですね〜

// Parent.vue
<template>
	<Child @click="onClick" />
</template>

<script>
import Child from './Child.vue';

export default {
	components: {
		Child,
	},
	methods: {
		onClick(value) {
			console.log('くりっく');
		},
	},
}
<script>
// Child.vue
<template>
	<button v-on="$listener"></button>
</template>

ただ、上記の状態だと@click.nativeという記法に書き換えられますし、なんなら.nativeを使う際は子コンポーネントに$listenersを書く必要を感じられません!(.nativeは子コンポーネント のルート要素のイベントを指します)

ただし.nativeに書き換えたとして、以下の例だとどうでしょう?

// Parent.vue
<template>
	<Child @click.native="onClick" />
</template>
// Child.vue
<template>
	<div>
		<input type="radio">
	</div>
</template>

ルート要素はdivなので、divのクリックイベントを取得してしまいますね...

DOMの構造に依存してしまいます、、。

そこで$listenersを使えばいいのです!

// Parent.vue
<template>
	<Child @click="onClick" />
</template>
// Child.vue
<template>
	<div>
		<input type="radio" v-on="$listeners">
	</div>
</template>

これでDOMの構造に依存することなく、inputのイベントを参照できます!

※ 参照できるイベントはclickに限らず、mouseoverといった他のイベントも参照できます!

Discussion