【Vue.js】子から親コンポーネントのイベント実行・データ受け渡し

# 子から親コンポーネントのデータを更新
初心者用記事です。Vue.jsに少し慣れたので忘れないうちに記事にします。

今回、二つの方法に触れます。
`$emit`で子から親コンポーネントの処理を呼び出す
`$emit`で子から親コンポーネントへ値を渡す

以下の記事を参考に追加機能付きで作成しました。
https://dev83.com/vue-emit/
分かりやすい記事なのでお勧めです。

親から子の受け渡しを行うアプリで、ボタンを押すと数が増減する簡単なアプリ作っていきます。
↓　例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/7c023ba8-4ea1-6f1b-1d01-53a2202a18e3.png)


実際に作ってもいいですが、JSFiddleで動作テストしてもいいと思います。

```vue
<div id="app">
</div>

new Vue({
        el: '#app',
        data: {
          // 下部に出てる合計個数
          totalCount: 0,
          items: [
            {
              name: 'りんご',
              price: 100,
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png'
            },
            {
              name: 'バナナ',
              price: 200,
             imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png'
            },
          ],
        },
      });
```


style部分

```.css
  .item-wrap {
    list-style-type: none;
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
  }
  .item-wrap li {
    padding: 1em;
  }
  .item-wrap img {
    border-radius: 50%;
  }
  .total-count {
    padding: 1em;
    background-color: #222;
    color: #fff;
  }
```

現状では何も表示されません。

# 簡単アプリを追加



本体に追加

```vue
<div id="app">
  <ul class="item-wrap">
    <item v-for="item in items" :item="item"></item>
  </ul>
  <div class="total-count">合計：{{ totalCount }}個</div>
</div>
```

子コンポーネント追加。ファイル読み込んだ方がいいですが今回はjsFiddle使って確認したので一つにまとめて書いてます。


```.js
Vue.component('item', {
        // 親から値もらう
        props: ['item'],
        // 子コンポーネントのテンプレート部分
        template: `
          <li>
            <img :src="item.imgUrl" style="height: 200px;">
            <div>{{ item.name }} = {{ childTotal }}個</div>
            <button @click="addCart">増やす</button>
            <button @click="removeCart">減らす</button>
          </li>
        `,
        // 子コンポーネントデータ
        data: function () {
          return {
            childTotal: 0
          };
        },
        // ボタン押すと発火する処理
        methods: {
          addCart: function () {
            this.childTotal += 1;
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
            }
          },
        },
      });

new Vue({
        // 省略。さっき書いたところ
      });
```

ここまでの状態だと子コンポーネントから親コンポーネントに値を渡していないので
「合計：個」には変化が起きません。

# 親コンポーネントのデータを更新
子コンポーネントのデータが更新されたら親コンポーネントのデータが更新されるようにします。

子コンポーネントのmethodsを修正。

```.js
methods: {
          addCart: function () {
            this.childTotal += 1;
            // ↓追加。親の合計個数増やす
            this.$emit('addbtn');
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
              // ↓追加。親の合計個数減らす
              this.$emit('removebtn');
            }
          },
        },
```

親コンポーネントを修正
itemコンポーネント部分
methods追加

```vue
<div id="app">
  <ul class="item-wrap">
    <item
          v-for="item in items"
          :item="item"
          @addbtn="addParentTotal"
          @removebtn="removeParentTotal"
          >
    </item>
  </ul>
  <div class="total-count">合計：{{ totalCount }}個</div>
</div>
```

```.js
new Vue({
        el: '#app',
        data: {
          totalCount: 0,
          items: [
            {
              name: 'RedApple',
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png'
            },
            {
              name: 'GreenApple',
              imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png'
            },
          ],
        },
        methods: {
          addParentTotal: function () {
            this.totalCount += 1;
          },
          removeParentTotal: function () {
            this.totalCount -= 1;
          },
        },
      });
```

最終的な子コンポーネント

```.js
      Vue.component('item', {
        props: ['item'],
        template: `
          <li>
            <img :src="item.imgUrl" style="height: 200px;">
            <div>{{ item.name }} = {{ childTotal }}個</div>
            <button @click="addCart">増やす</button>
            <button @click="removeCart">減らす</button>
          </li>
        `,
        data: function () {
          return {
            childTotal: 0
          };
        },
        methods: {
          addCart: function () {
            this.childTotal += 1;
            this.$emit('addbtn')
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
              this.$emit('removebtn');
            }
          },
        },
      });
```

# ひとまず完成
![vue-emit.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/c715198e-f08e-d3d4-228a-b4981f7169d2.gif)



`@addbtn=”addParentTotal”
@removebtn=”removeParentTotal”`
親コンポーネントの`<item>~~~</item>`に追加したのはこのふたつ。

子コンポーネントの`this.$emit('addbtn')`
もしくは`this.$emit('removebtn');`が発火したら
親コンポーネントのmethodsにある`addParentTotal` `removeParentTotal`
を呼び出してくださいという処理です。


# 子から親に値を受け渡す
現状だと親コンポーネントで合計を計算していますが、子コンポーネントからの`値を直接受け取り`、計算したい場合もあると思います。


## サンプル：値段を計算
親コンポーネントに値段を定義して、子コンポーネントから受け取った個数をもとに合計金額を計算する機能を追加します

受け渡しは引数を追加してやります。

```
this.$emit('addbtn', this.childTotal, name)
```
この場合だとthis.childTotalとnameを渡しています。
親の受け取る側

```
addParentTotal: function (total, name){
}
```

これで子から親へデータを渡すことができます。


サンプルを作ったのですが、 改造してたら意外とシンプルにならずに申し訳ないです…

```vue
<div id="app">
  <ul class="item-wrap">
    <item
          v-for="item in items"
          :item="item"
          @addbtn="addParentTotal"
          @removebtn="removeParentTotal"
          >
    </item>
  </ul>
  <div class="total-count">合計：{{ totalCount }}個</div>
  <p class="total-count" v-for="item in items">
    {{ item.name }}の合計金額：￥{{ item.totalYen }}
  </p>
</div>
```

子コンポーネント

```.js
Vue.component('item', {
  props: ['item'],
  template: `
    <li>
      <img :src="item.imgUrl" style="height: 200px;">
      <div>{{ item.name }} = {{ childTotal }}個</div>
      <button @click="addCart(item.name)">増やす</button>
      <button @click="removeCart(item.name)">減らす</button>
    </li>
  `,
  data: function () {
    return {
      childTotal: 0
    };
  },
  methods: {
    addCart: function (name) {
      this.childTotal += 1;
      this.$emit('addbtn', this.childTotal, name)
    },
    removeCart: function (name) {
      if (this.childTotal > 0) {
        this.childTotal -= 1;
        this.$emit('removebtn', this.childTotal, name);
      }
    },
  },
});
```
親コンポーネント

```.js
new Vue({
        el: '#app',
        data: {
          totalCount: 0,
          items: [
            {
              name: 'リンゴ',
              price: 100,
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png',
              number: 0,
              totalYen: 0
            },
            {
              name: 'バナナ',
              price: 200,
              imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png',
              number: 0,
              totalYen: 0
            },
          ],
        },
        methods: {
          addParentTotal: function (total, name) {
            this.totalCount += 1;
            // 名前一致する該当の要素をオブジェクトから入れる
            const item = this.items.find(ele => ele.name === name)
            item.number = total
            item.totalYen = total * item.price
          },
          removeParentTotal: function (total, name) {
            this.totalCount -= 1;
            // 名前一致する該当の要素をオブジェクトから入れる
            const item = this.items.find(ele => ele.name === name)
            item.number = total
            item.totalYen = total * item.price
          },
        },
      });
```

```.css
  .item-wrap {
    list-style-type: none;
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
  }
  .item-wrap li {
    padding: 1em;
  }
  .item-wrap img {
    border-radius: 50%;
  }
  .total-count {
    padding: 1em;
    background-color: #222;
    color: #fff;
  }
```

それぞれのオブジェクトに個数を操作。
また、1個当たりの値段から金額を計算するアプリ完成。
![vue-emit2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/c4922616-511a-e48c-043a-cf1f98a27a03.gif)



# これなにに使えるの？
色々ありますけど、モーダルなどが分かりやすい例です。
モーダルを子コンポーネントにして、フォームの入力をしてもらうと親の値も変わるみたいな感じです。

それについては次の機会に