Vue.jsで孫 → 親コンポーネントへの値の受け渡しを楽に行う方法
サマリ
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