これは、個人ブログ からの転用です ROXXに入って学んだことの1つです。
概要
Vue.js で 親コンポーネントの method 実行させたい場合、$emit
使ってイベントを発火させるより、
props
に method をコールバックとして登録しておいて実行させたほうが以下のメリット上げられるので「こっちのほうが良くね?」って話です
props
の成約をつけられる(requird, etc)$emit
の文字列を管理しなくていい- IDEで補完が効く
実際のコード
ボタン押したらカウントアップしていくようなやつ
呼び出し側の親コンポーネント
<template> <div> <div>{{ count }}</div> <child :handle-add-number="addCount" @addNumber="addCount" /> </div> </template> <script> import child from "./child"; export default { components: {child}, data: () => ({ count: 0 }), methods: { addCount(number) { return this.count += number } } } </script>
<template> <button @click="countUpProps()">count up props</button> <button @click="countUpEmit()">count up emit</button> </template> <script> export default { props: { handleAddNumber: { type: Function, required: true // required をつけて必須に } }, methods: { // Prop で渡した function を実行 countUpProps() { this.handleAddNumber(1) }, // emit を使って function を実行 countUpEmit() { // emit の event は 文字列で管理 this.$emit('addNumber', 1) } } } </script>
props で定義しておけばこんなふうにIDEで補完が効きます
こんなかんじで、props
を使うと、
requird で縛れて、method の渡し忘れを防げたり、
$emit
の文字列管理しなくていいかつ、IDEで補完が効くのでその分typo が防げる
という点で開発しやすくなるかなと思います。
TS でやると
Vue も Vue3 から、TypeScript を正式にサポートということで未来を見据えて、
同じ処理を Vue3 + TypeScript で書いてみます
呼び出し側の親コンポーネント
<template> <div> <p>{{ count }}</p> <child :handle-add-num="addNumber" @addNumber="addNumber" /> </div> </template> <script lang="ts"> import {defineComponent, ref} from 'vue'; import child from "./child.vue"; import AddNumberInterface from "../types/AddNumberInterface"; //function の Interface export default defineComponent({ components: { child }, setup() { const count = ref<number>(0) //Interface を指定して関数を定義 const addNumber: AddNumberInterface = (num: number) => { return count.value += num } return { //data count, //function addNumber } } }) </script>
props で渡す関数の Interface 定義
export default interface AddNumberInterface { (num: number): number }
<template> <div> <button @click="countUpProps()">count up props</button> <button @click="countUpEmit()">count up emit</button> </div> </template> <script lang="ts"> import { PropType, defineComponent , SetupContext} from 'vue'; import AddNumberInterface from "../types/AddNumberInterface"; type Props = { handleAddNum: AddNumberInterface; //Prop の Interface を定義 } export default defineComponent({ props: { handleAddNum: { // PropType を使って Prop の type に使いたい関数のInterface を指定 type: Function as PropType<AddNumberInterface>, required: true } }, setup(props: Props, context: SetupContext) { const countUpProps = () => { props.handleAddNum(1) } const countUpEmit = () => { context.emit('addNumber', 1) } return { countUpProps, countUpEmit, } } }) </script>
PropType(これは Vue2.6 からあったはず?) と TypeScript を使うことにより、props
の Typeを独自のInterfaceに変更することができ、
どんな functionを渡せばいいか、明示することができました!
「Vue ぽくない」とか言われそうですが、型
という概念が好きな私にとっては、とても書きやすく感じました。
まとめ
公式のリファレンスや、色々な書籍でもVueで親のmethod を実行した場合は、$emit
を使う、
というのは当たり前のように記載されていますが、実際書き比べてみたり、実際のコードを運用してみた観点からしても、
props で method を渡したほが、明示的かつケアレスミスを減らすことができて、とても良いと感じています。
$emit
を使う意義を感じなくなってきているので、$emit
を使う理由や、もっとよい方法があれば教えてもらえるとありがたいです。
未来のことはわかりませんが、Vue3 からは TypeScript サポートされより型を意識した開発をするように今後なっていくのだとしたら、
上記のような書き方はさらに恩恵を受けそうだなと思っています。