親コンポーネントから子コンポーネントにデータを渡すには、propsプロパティを使います。詳しくは「【Vue.js】propsを使う時は命名に注意。サンプルコード有」に書きました。
では、反対に子コンポーネントから親コンポーネントのデータを更新するにはどうしたらいいでしょうか。結論から言うと、$emitでイベントをトリガ(きっかけとなるもの)して、v-onディレクティブでイベントを購読することで実現可能です。
文章だけだとよくわからないと思いますので、コードを書きながら子から親コンポーネントのデータを更新する方法を学んでいきましょう。
目次
子から親コンポーネントのデータ更新を実装したサンプルを確認してみよう
子から親コンポーネントのデータ更新を実装して、以下のようなサンプルアプリを作ってみましょう。
簡単に画面の説明をします。「増やす」「減らす」ボタンをクリックすると、RedApple、GreenApple、それぞれの個数が増減します。この数値は子コンポーネントのdataプロパティを参照しています。
さらに、RedAppleとGreenAppleの増減に合わせて合計数が表示されます。こちらの数値は親コンポーネントのdataプロパティを参照しています。

このようなサンプルアプリを使って解説していきます。
子コンポーネント内のdataプロパティを更新する機能を実装しよう
以下の基本テンプレートに機能を追加していきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <style> .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; } </style> </head> <body> <div id="app"> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> </script> </body> </html> |
Vueインスタンスのdataプロパティの中にfruitsプロパティを作ろう
Vueインスタンスを生成して、dataプロパティの中にfruitsプロパティを作ります。fruitsプロパティにはRedAppleとGreenAppleの情報が入っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <!doctype html> <html lang="ja"> <head> <!-- 省略 --> </head> <body> <div id="app"> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> // ここから追加します new Vue({ el: '#app', data: { fruits: [ { name: 'RedApple', imgUrl: 'https://placehold.jp/18/c40000/ffffff/150x150.png?text=RedApple' }, { name: 'GreenApple', imgUrl: 'https://placehold.jp/18/79cf2d/ffffff/150x150.png?text=GreenApple' }, ], }, }); // ここまで追加します </script> </body> </html> |
グローバルにitemコンポーネントを登録しよう
グローバルにitemという名前のコンポーネントを登録します。dataプロパティには、subtotalCountというプロパティを登録しています。subtotalCountでは、RedApple、GreenAppleそれぞれの個数を管理しています。
templateの「増やす」「減らす」ボタンをクリックすると、methodsが呼び出されて、subtotalCountの値が変化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <!doctype html> <html lang="ja"> <head> <!-- 省略 --> </head> <body> <div id="app"> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> // ここから追加します Vue.component('item', { props: ['fruit'], template: ` <li> <img :src="fruit.imgUrl"> <div>{{ fruit.name }} = {{ subtotalCount }}個</div> <button @click="increase">増やす</button> <button @click="reduce">減らす</button> </li> `, data: function () { return { subtotalCount: 0 }; }, methods: { increase: function () { this.subtotalCount += 1; }, reduce: function () { if (this.subtotalCount > 0) { this.subtotalCount -= 1; } }, }, }); // ここまで追加します new Vue({ // 省略 }); </script> </body> </html> |
itemコンポーネントを使用しよう
itemコンポーネントを<div id=”app”></div>の中に記述して、ブラウザで開いてみてください。RedAppleとGreenAppleの増減ができるようになっていると思います。合計個数はまだ動きません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!doctype html> <html lang="ja"> <head> <!-- 省略 --> </head> <body> <div id="app"> <!-- ここから追加します --> <ul class="item-wrap"> <item v-for="fruit in fruits" :fruit="fruit" > </item> </ul> <div class="total-count">合計:{{ totalCount }}個</div> <!-- ここまで追加します --> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> // 省略 </script> </body> </html> |
子コンポーネントでイベントが発火したら、親コンポーネントのデータが更新される機能を実装しよう
トリガを設定しよう
子コンポーネントのincreaseメソッド(増やすボタン)、reduceメソッド(減らすボタン)が呼び出された時に発火するトリガを設定しましょう。
this.$emit(‘clicked-increase-button’);で「clicked-increase-button」というイベント名のトリガを設定しています。
this.$emit(‘clicked-reduce-button’);で「clicked-reduce-button」というイベント名のトリガを設定しています。
$emitの構文
$emit(イベント名, [引数])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <!doctype html> <html lang="ja"> <head> <!-- 省略 --> </head> <body> <div id="app"> <!-- 省略 --> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> Vue.component('item', { // 省略 methods: { increase: function () { this.subtotalCount += 1; // ここから追加します this.$emit('clicked-increase-button'); // ここまで追加します }, reduce: function () { if (this.subtotalCount > 0) { this.subtotalCount -= 1; // ここから追加します this.$emit('clicked-reduce-button'); // ここまで追加します } }, }, }); // 省略 </script> </body> </html> |
イベントを購読しよう
itemコンポーネントに以下を追加します。
@clicked-increase-button=”incrementTotalCount”
@clicked-reduce-button=”decrementTotalCount”
@clicked-increase-button=”incrementTotalCount”は、子コンポーネントで先ほど設定したthis.$emit(‘clicked-increase-button’);が発火したら、incrementTotalCountメソッドを呼び出してね、という意味です。
@clicked-reduce-button=”decrementTotalCount”も同様です。
これで、RedAppleとGreenAppleの増減に合わせて、合計値も増減するようになったと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | <!doctype html> <html lang="ja"> <head> <!-- 省略 --> </head> <body> <div id="app"> <ul class="item-wrap"> <!-- itemコンポーネントを書き換えます --> <item v-for="fruit in fruits" :fruit="fruit" @clicked-increase-button="incrementTotalCount" @clicked-reduce-button="decrementTotalCount" > </item> <!-- itemコンポーネントを書き換えます --> </ul> <div class="total-count">合計:{{ totalCount }}個</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> // 省略 new Vue({ el: '#app', data: { // ここから追加します totalCount: 0, // ここまで追加します fruits: [ // 省略 ], }, // ここから追加します methods: { incrementTotalCount: function () { this.totalCount += 1; }, decrementTotalCount: function () { this.totalCount -= 1; }, }, // ここまで追加します }); </script> </body> </html> |
コードの全容
コードの全容はJSFiddleでご確認いただけます。URLはこちらです。