Vueでグローバルイベントを使う

318
NO IMAGE

Vueなどで画面を作っていると、時々画面コンポーネントの親子関係を飛び越えたイベント送信を行いたくなる場合がある。例えば汎用のメッセージ表示ダイアログや確認ダイアログの表示処理を最上位コンポーネントで定義し、その呼び出しイベントをコンポーネント階層の末端から行いたい場合など。

通常ではコンポーネントにイベントリスナーを定義してどんどん階層を遡る定義をする必要があるが、流石にこれは漏れが発生しがちだし調査も大変なので、どこからイベントを発火しても基底のコンポーネントのイベントハンドラーが動作するような仕組みの方が漏れはなくなる。

過去のVueにはEvent Busの仕組みがあったが、最近のバージョンでは削除されている。Event Busは複雑になりやすいからというのが理由のようだが、一方で単にイベントを上位にスルーするだけの定義を何回も書くのもそれはそれで問題。

結論を書けば、$root.$emitでイベントを送信し、イベントを受け取りたいコンポーネントで$root.$onでイベントを受け取ればよい。以下は簡単な定義の例。

<template>
  <b-container fluid>
    <b-modal ref="messageDialog" title="何らかのメッセージ">{{message}}</b-modal>
  </b-container>
</template>

<script lang="ts">
import Vue, { Component as VueComponent, VNode } from 'vue'
import { Component } from 'vue-property-decorator'
import { BModal } from 'bootstrap-vue'

class Foo extends Vue {
  message = ''

  mounted () {
    this.$root.$on('MessageEvent', this.onMessage)
  }

  async onMessage (event: MessageEvent) {
    this.message = event.message
    this.$ref['messageDialog'].show()
  }
}
</script>

実際にはこのコンポーネントを階層の最上位において、すべてのページがこのコンポーネントの子孫という形にすることになる。Atomic Componentsでいうとecosystemsレベルでイベントとハンドラーを定義している形か。