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

# サマリ

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

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

# $emitと$listenersの比較

## `$emit`を使った場合

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

<script>
export default {
	components: {
		Child,
	},
	methods: {
		onClick(value) {
			alert(value);
		},
	},
}
<script>
```

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

<script>
export default {
	components: {
		GrandChild
	},
	methods: {
		onClick(value) {
			this.$emit('click', value);
		}
	},
}
<script>
```

```vue
// 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`を使った場合

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

<script>
export default {
	components: {
		Child,
	},
	methods: {
		onClick(value) {
			alert(value);
		},
	},
}
<script>
```

```vue
// Child.vue
<template>
	<GrandChild @click="$listeners['click']" />
</template>
```

```vue
// 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['']`の中に記載するのは親で受け取りたいイベント名を記載します

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

## まとめ

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

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

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

## おまけ

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

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

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

export default {
	components: {
		Child,
	},
	methods: {
		onClick(value) {
			console.log('くりっく');
		},
	},
}
<script>
```

```vue
// Child.vue
<template>
	<button v-on="$listener"></button>
</template>
```

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

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

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

```vue
// Child.vue
<template>
	<div>
		<input type="radio">
	</div>
</template>
```

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

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

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

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

```vue
// Child.vue
<template>
	<div>
		<input type="radio" v-on="$listeners">
	</div>
</template>
```

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

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

### もっと学びたい人向け

この辺のもう少しお得な書き方が↓に載ってますので、興味のある方はご覧ください。
[https://qiita.com/shinobu_shiva/items/e0c458aa6c1e683a9881](https://qiita.com/shinobu_shiva/items/e0c458aa6c1e683a9881)