Vueのpropsと$emitを使ってコンポーネント間でデータをやりとりする

# はじめに

Vue で親コンポーネントと子コンポーネントの間でデータをやり取りする方法について、今まで何となくやっていたので改めて勉強しました。
分かりにくい部分だと思うので、よかったら参考にして頂ければと思います。

# 対象者

* 基本的な JavaScript のコーディングができる方
* Vue で開発をしたことがある方

# 環境

* Vue.js 2.6.10
* Vuetify 2.1.0

# 勉強しながら作ったもの

名前と血液型を入力する子コンポーネントを作りました。
親コンポーネントから初期値を渡して初期表示したり、子コンポーネントで入力した名前・血液型を親コンポーネントに渡したりできるようにしています。

下の画像がスクリーンショットで、氏名から【反映】ボタンまでの３段が子コンポーネントで、`parent:` の段が親コンポーネントです。

![スクリーンショット 2021-05-22 17.03.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/5eb847ae-c4fe-283c-f0c7-d872409feb65.png)

# 親子間のデータやりとりについて

作ったものの説明の前に親子間のデータやり取りの基本に触れておきます。

## 親 → 子 のデータフロー

親は子に渡すデータを属性として指定します。
子はそれを `props` で受け取ります。
下記の例では、名前を `value` で親子間で一致させています。

```vue:親
<child :value="param"></child>
```

```vue:子
<script>
export default {
  props: {
    value: {
      name: String,
      bloodType: String
    }
  }
}
</script>
```

## 子 → 親 のデータフロー

子は親に渡すデータを `$emit` で渡します。
`$emit` で親側のイベントを発火させて、データが渡ります。

下記の場合、子で `$emit` を実行すると `this.inputs` を引数として親の `input` イベントが発火します。親側では `value` に子が指定した引数が渡ります。

```vue:親
<child @input="value => param = value"></child>
```

```js:子
this.$emit('input', this.inputs)
```

## 親コンポーネントの記述をまとめる

上記の `親 → 子` `子 → 親` で説明した親コンポーネントの実装内容をまとめると次のようになります。

```vue:親
<child
  :value="param"
  @input="value => param = value"
>
</child>
```

なお次のように `v-model` を使うと属性一つで記述できます。

```vue:親
<child v-model="param"></child>
```

# 作ったものの説明

それぞれのコンポーネント内のデータの状態を確認したいので、本来の入力項目以外に `{{ }}` を使ってデータをダンプしています。

## 子コンポーネント

<details><summary>コード</summary><div>

```vue:Child.vue
<template>
  <v-row>
    <v-col>
      <v-text-field v-model="name"></v-text-field>
      <v-radio-group v-model="bloodType" row>
        <v-radio
          v-for="elem in bloodTypes"
          :key="elem.value"
          :label="elem.label"
          :value="elem.value"
        ></v-radio>
      </v-radio-group>
      <v-btn @click="handleClick">反映</v-btn>
      {{ inputs }}
    </v-col>
  </v-row>
</template>

<script>
export default {
  props: {
    value: {
      name: String,
      bloodType: String
    }
  },
  data: () => ({
    name: '',
    bloodType: '',
    bloodTypes: [
      { label: 'A型', value: 'a' },
      { label: 'B型', value: 'b' },
      { label: 'O型', value: 'o' },
      { label: 'AB型', value: 'ab' }
    ]
  }),
  computed: {
    inputs: function () {
      return {
        name: this.name,
        bloodType: this.bloodType
      }
    }
  },
  created () {
    this.name = this.value.name
    this.bloodType = this.value.bloodType
  },
  methods: {
    handleClick () {
      this.$emit('input', this.inputs)
    }
  }
}
</script>
```
</div></details>

`<template>` 側

* 【反映】ボタンを押すと `handleClick` 関数が実行され、`$emit` により親コンポーネントに入力内容が伝わります

`<script>` 側

* `props` の `value` に、親コンポーネントで指定した値（初期値）が設定されます
  * 親側では `:value` や `v-model` で初期値を指定します
* `created` で親コンポーネントから指定された値を `data` 配下にバラして設定しています
* `computed` の `inputs` は、子コンポーネントで入力された値を親コンポーネントへ反映するためのオブジェクトです
* `methods` の `handleClick` 関数で `this.$emit` して親コンポーネントに反映します
  * 反映ボタンを押したときに動きます


## 親コンポーネント

<details><summary>コード</summary><div>

```vue:Parent.vue
<template>
  <v-container>
    <v-row>
      <v-col>
        <child v-model="param"></child>
        parent: {{ param }}
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import Child from './Child'

export default {
  components: {
    'child': Child
  },
  data: () => ({
    param: {
      name: 'hoge',
      bloodType: 'a'
    }
  })
}
</script>
```
</div></details>

`<template>` 側

* 子コンポーネントを使用します
    * `v-model` で子コンポーネントに渡す値を指定します
    * `:value` でも値を渡すことができます
        * その場合は前述の通り `@input` 指定も必要になります

`<script>` 側

* `import` で子コンポーネントをインポートします
* `components` で子コンポーネントをコンポーネント定義します
* `data` には子コンポーネントに初期値として渡す値を定義します
    * 子コンポーネントで値が書き換えられて `$emit` されると、ここが書き換わります


# 動かしてみる

名前や血液型を入力して【反映】を押すと、親コンポーネントに入力内容が伝わります。
ダンプされている内容から確認できると思います。

* 【反映】ボタンの右は、子コンポーネント側の値
* `parent:` の右は、親コンポーネント側の値

![ezgif-3-c4b39a16f4c1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/43c564da-9da7-7077-a5e5-20e1d652bd44.gif)

# 処理の流れ

1. 氏名や血液型の欄を編集する
    1. 子コンポーネントの `data` の中が書き換わる
    1. この時点では親コンポーネントには反映されない
1. 【反映】ボタン押す
    1. 子コンポーネントの `handleClick` 関数により `$emit` される
    1. 子コンポーネントで入力された値が、親コンポーネントに伝わる

# 所感

今回勉強したことで、今まで何となくだった理解が深まって良かったと思っています。でもやっぱりしばらくするとまた忘れそうなので、記事見て思い出そうと思います。