【Vue.js】$emitメソッドの正体　コンポーネント(子→親)の間のデータ受け渡しでやってる事

## はじめに
仕事で使う事になったので1からVue.jsについて学んだ。
ちゃんと覚えておかないとまずそうな事を備忘録として1つ1つ残しておく。

## $emitメソッドの正体　コンポーネント(子→親)の間のデータ受け渡しでやってる事
カスタムイベントを定義・発火させることができるメソッド[^1]。
このメソッドを応用して、コンポーネント間（子→親）のデータの受け渡しを行う事ができる。
![ezgif.com-gif-maker (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/39fc1f53-a2c7-33da-9d35-fc4417b1f743.gif)
[^5]: ソースコード全体を見たい場合は[ここ](https://github.com/yuta-katayama-23/intro-vuecli/tree/bd97b33e82ad94f76b4734f5001168f662b808cd)
動画のソースコードは以下[^5]。

```vue:parent-component.vue
<template>
  <div>
    <LikeHeader></LikeHeader>
    <h2>{{ number }}</h2>
    <LikeNumber :total-number="number" @my-click="number = $event"></LikeNumber>
  </div>
</template>

<script>
import LikeHeader from "./components/LikeHeader.vue";

export default {
  data() {
    return {
      number: 10,
    };
  },
  components: {
    LikeHeader,
  }
};
</script>

<style scoped>
div {
  border: 1px solid blue;
}
</style>
```
```vue:child-component.vue
<template>
  <div>
    <p>いいね({{ halfNumber }})</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
export default {
  props: ["totalNumber"],
  computed: {
    halfNumber() {
      return this.totalNumber / 2;
    },
  },
  methods: {
    increment() {
      this.$emit("my-click", this.totalNumber + 1);
    },
  },
};
</script>

<style scoped>
div {
  border: 1px solid red;
}
</style>
```

ここでは、`child-component.vue`(子)から`parent-component.vue`(親)へデータの受け渡しを行っている。
上記の考え方としては、

1. `child-component.vue`の方で`$emitメソッド`でカスタムイベント`my-click`を定義し発火させる
1. その際に`$emitメソッド`では第二引数を取る事ができるので、第二引数に値`this.totalNumber + 1`を渡すようにする
1. `parent-component.vue`では、v-onディレクティブでカスタムイベント`my-click`が発火した際に何らかの処理を行うように設定する事を考える
1. v-onディレクティブの`@hoge=""の""`はJavaScriptを書く事ができるのでそこで`parent-component.vue`のdataプロパティの値を`$event`の値に変更するようにする

というもの。`$emitメソッド`でカスタムイベントを発火させた場合、`$event`には`$emitメソッド`の第二引数[^2]の値が渡る仕様になっているので、上記のような考え方で実装する事で、`$emitメソッド`を応用して「子→親のコンポーネント間データ渡し」が実現できる。

**※補足**
`$emitメソッド`では第二引数は任意の引数で省略可能[^3]。
つまり、`$emitメソッド`は純粋にカスタムイベントを定義・発火させるためのメソッドであり、**オプションとして第二引数にデータを設定するとイベント発火時に$eventにデータを渡す事ができるというだけ**。
子→親のデータの受け渡しは$emitメソッドのカスタムイベント発火機能に便乗して実現されている。

[^1]: Vueインスタンスに標準で定義されているもの
[^2]: interfaceを見ると`$emit(event: string, ...args: any[]): this;`となっている<br>第二引数は`...args: any[]`で、これはarrayだが1つの要素しかない場合はその値が`$event`に渡される
[^3]: 上記の例で言えば、単に`this.$emit("my-click");`と書く事もできるという事

### `@my-click="number = $event"`の書き換え
`@my-click="number = $event"`は以下のような実装に書き換える事もできる。

```vue:sample.vue
<template>
  <!-- ・・・ -->
    <LikeNumber :total-number="number" @my-click="increaseNumber"></LikeNumber>
  <!-- ・・・ -->
</template>

<script>
  // ・・・
  },
  methods: {
    increaseNumber(value) {
      this.number = value;
    },
  },
  // ・・・
</script>
```

※v-onディレクティブのJavaScriptの部分がmethodsプロパティで定義された関数でかつその引数が`$event`しかない場合は省略可能なので、上記の実装では`@my-click="increaseNumber"`と書いている。

### propsのように簡単でないのはなぜ？
`props`を用いると、親→子のコンポーネント間データ渡しが簡単にできるが、その逆（子→親）はなぜ$emitメソッドを使うなど面倒な事をしなければ実現できないのか？という事を思うだろう。
これはVue.jsが、単一方向のデータ渡ししかできないようにする事で、データフローを親→子へのみに限定し、データフローの複雑化を防止してシンプルになるようにしているため。
つまり、Vue.jsのコンポーネントでは、

- 子は親(のdata)に依存する
- 親は子(のdata、props)に依存しない

という設計がなされている。

※ちなみに、子コンポーネントで親から渡ってきたデータを変更すると

```
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "totalNumber"（親コンポーネントが再レンダリングされるたびに値が上書きされるため、プロップを直接変更することは避けてください。 代わりに、小道具の値に基づいてデータまたは計算されたプロパティを使用してください。 変更されるプロップ： "totalNumber"）
```

というエラーが出る。
Vue.jsでは親→子のコンポーネント間データ受け渡しをする（子が親に依存する）場合、子側で親から渡ってくるデータを変更しないようにオススメしている。
![ezgif.com-gif-maker (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/3131c73e-a31a-6b91-65ec-c1ee14cec114.gif)

### propsでオブジェクト・配列を渡す時は参照渡しになるのでより注意する
オブジェクト・配列を変数に格納すると参照渡しになるように、Vue.jsでも親→子でコンポーネント間データ渡しでオブジェクト・配列をpropsで渡すと参照渡しになってしまう。
この場合、子コンポーネントでpropsで渡ってきた値を書き換えた時、親のdataの値も書き換えられるという影響が出るので注意が必要。

※基本的には上記で述べたように、**子側で親から渡ってくるデータを変更しない**が原則なので、予期せぬバグを発生させないためにも子側で親から渡ってきたpropsを変更しないようにする。

### $emitメソッドのカスタムイベント名はケバブケースで
カスタムイベントはHTML上[^4]に書く事になるので、基本的にはHTML側の慣例にならいケバブケースで書くのがオススメ。
[^4]: 正確にはsingle file componentの`template`

## Vue.jsの勉強メモ一覧記事へのリンク
Vue.jsについて勉強した際に書いた勉強メモ記事のリンクを集約した記事。

- https://qiita.com/yuta-katayama-23/items/dabefb59d16a83f1a1d4