アキバのブログ

エンジニアやってます

Vue.js $emit 使わないで props で method 渡したほうが良くない?

概要

Vue.js で 親コンポーネントの method 実行させたい場合、$emit 使ってイベントを発火させるより、
props に method をコールバックとして登録しておいて実行させたほうが以下のメリット上げられるので「こっちのほうが良くね?」って話です

  • props の成約をつけられる(requird, etc)
  • $emit の文字列を管理しなくていい
  • IDEで補完が効く

実際のコード

ボタン押したらカウントアップしていくようなやつ f:id:akki_megane:20200708203420p:plain

呼び出し側の親コンポーネント

<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で補完が効きます f:id:akki_megane:20200708203603p:plain


こんなかんじで、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 サポートされより型を意識した開発をするように今後なっていくのだとしたら、 上記のような書き方はさらに恩恵を受けそうだなと思っています。