VueJS 速習チュートリアル

Vue $emit() メソッド

1. $emit() メソッドとは

Vue に組み込まれている $emit() メソッドを使用すると、子コンポーネント から カスタムイベント を作成し、それを 親要素(親コンポーネント) でキャッチできるようになります。

Props(プロップス) が親要素から子コンポーネントへデータを送るために使われるのに対し、$emit() はその逆、つまり子コンポーネントから親へ情報を渡すために使用されます。

これから行う実装の 目的 は、食べ物アイテムの「お気に入り(favorite)」ステータスの変更を、子コンポーネントである FoodItem.vue ではなく、親コンポーネントである App.vue 側で行うようにすることです。

なぜ FoodItem.vue ではなく App.vue で変更する必要があるのでしょうか? それは、お気に入りステータスのデータがそもそも App.vue に保存されている「信頼できる唯一の情報源(Source of Truth)」だからです。大規模なプロジェクトでは、データは App.vue が接続しているデータベースから取得されることが多く、コンポーネント側で発生した変更をデータベースに反映させるには、子から親へと通信(バックフロー)してデータを同期させる必要があるのです。

2. カスタムイベントの発行

コンポーネントから親へ情報を送る必要がある場合、組み込みメソッドの $emit() を使用します。

すでに FoodItem.vue コンポーネント内には、トグルボタンがクリックされたときに実行される toggleFavorite メソッドがあります。既存のロジックを削除し、カスタムイベント toggle-favorite を発行(emit)するコードを追加しましょう。

FoodItem.vue:

methods: {
  toggleFavorite() {
    // カスタムイベント 'toggle-favorite' を発行
    this.$emit('toggle-favorite');
  }
}

カスタムイベントの名前は自由に決められますが、Vue の慣習として ケバブケース(kebab-case) を使用するのが一般的です。

3. カスタムイベントの受信

FoodItem.vue から toggle-favorite イベントが発行されるようになりました。次に、親である App.vue でこのイベントを リスニング(監視) し、イベントが発生したことを確認するためのメソッドを呼び出す必要があります。

親コンポーネントのテンプレート内でコンポーネントを作成する際、v-on: の省略記法である @ を使用してイベントをリッスンします。

3.1 実装例:App.vue でのイベント受信

App.vuetoggle-favorite イベントをリッスンするコード:

<food-item
  v-for="x in foods"
  :key="x.name"
  :food-name="x.name"
  :food-desc="x.desc"
  :is-favorite="x.favorite"
  @toggle-favorite="receiveEmit"/>

カスタムイベントが発生したときに実行される receiveEmit メソッドを App.vue に作成します。

methods: {
  receiveEmit() {
    alert('カスタムイベントを受信しました!');
  }
}

4. 親コンポーネントで「お気に入り」ステータスを変更する

これで、子コンポーネントの「お気に入り」ボタンがクリックされたときに App.vue に通知が届くようになりました。

次に、ボタンがクリックされた際に App.vuefoods 配列内にある該当アイテムの favorite プロパティを変更します。そのためには、どのアイテムがクリックされたかを特定できるよう、FoodItem.vue から App.vue へ、一意な情報である「食べ物名」を引数として送ります。

FoodItem.vue:

methods: {
  toggleFavorite() {
    // 第2引数に foodName を含めて発行
    this.$emit('toggle-favorite', this.foodName);
  }
}

これで、App.vue 側ではイベント発生時に呼び出されるメソッドの引数として、食べ物名を受け取ることができます。

App.vue:

methods: {
  receiveEmit(foodId) {  
    alert('クリックされたアイテム: ' + foodId);
  }
}

どのアイテムがクリックされたかが判明したので、foods 配列内の該当するオブジェクトを更新します。

App.vue:

methods: {
  receiveEmit(foodId) {
    // findメソッドを使って、クリックされた名前と一致するオブジェクトを検索
    const foundFood = this.foods.find(
      food => food.name === foodId
    );
    // favorite ステータスを反転(true <-> false)
    foundFood.favorite = !foundFood.favorite;
  }
}

上記のコードでは、JavaScript の配列メソッド find を使用して、foods 配列の中からクリックされたアイテム名と一致する name プロパティを持つオブジェクトを探し、foundFood として取得しています。その後、そのアイテムの favorite プロパティをトグルさせています。

これで foods 配列内のデータが正しく更新されました。最後に、お気に入りを示す画像(インジケーター)が連動して更新されるようにします。

各コンポーネントは、すでに App.vue から foods 配列のステータスを is-favorite プロップスとして受け取っています。そのため、FoodItem.vue 側では <img> 要素の v-show でこの isFavorite プロップスを直接参照するだけで、表示が自動的に更新されます。

<img src="/img_quality.svg" v-show="isFavorite">

FoodItem.vue 内で以前使用していたローカルのデータプロパティ foodIsFavorite は不要になったため削除します。これで、データ管理が親コンポーネントに集約されました。

5. 'emits' オプション

FoodItem.vue 内で Props を宣言するのと同様に、コンポーネントが何を発行(emit)するかを Vue の emits オプションを使って明示的に記述できます。

Props は宣言が必須ですが、emits は「ドキュメント化」として強く推奨されるものです。

5.1 実装例:FoodItem.vue での宣言

<script>
export default {  
  props: ['foodName', 'foodDesc', 'isFavorite'],
  // 発行するカスタムイベントをリストアップ
  emits: ['toggle-favorite'],
  methods: {
    toggleFavorite() {
      this.$emit('toggle-favorite', this.foodName);
    }
  }
};
</script>

このように発行するイベントをドキュメント化しておくことで、他の開発者がそのコンポーネントを再利用する際、どのようなイベントをリッスンすべきかが一目で分かるようになり、開発効率が向上します。