【Vue.js】通知するイベントの種類の決定権を親コンポーネントに委譲する

  • 2020年6月4日
  • Vue

 Vue.jsで親コンポーネントに対して子コンポーネント上でイベントが起きたと通知する仕組みにemitがあります。
API — Vue.js#$emit

Vue.component('welcome-button', {
  // $emit('welcome')で親コンポーネントにwelcomeという名のイベント発生を通知します
  template: `
    <button v-on:click="$emit('welcome')">
      Click me to be welcomed
    </button>
  `
})

<div id="emit-example-simple">
  // welcomeイベント発生時にsayHiメソッドを実行します
  <welcome-button v-on:welcome="sayHi"></welcome-button>
</div>

 Vue.jsでは時折HTMLElementをラッピングしたコンポーネントがあり、そういったコンポーネントに望まれるイベントはラップ対象のイベントそのままです。例えば次です。

<template>
  <label>
    {{ label }}
    <input
        type="text"
        @click="$emit('click', $event)"
        @change="$emit('change', $event)"
        @input="$emit('input', $event)"
        @focus="$emit('focus', $event)"
        @blur="$emit('blur', $event)"
        @keydown="$emit('keydown', $event)"
        @keypress="$emit('keypress', $event)"
        @keyup="$emit('keyup', $event)"
    >
  </label>
</template>
<script>
export default {
  props: {
    label: { type: String, default: "" },
  },
};
</script>

 上述のコードでは途中で切り上げましたが全てのイベントを通知しようとするときりがありません。また、このイベント通知はVue.js内のJavaScriptコードの実行であり計算資源の無駄です(具体的にどの程度資源を使うのか確かめていないので無視できる程度の無駄づかいかも?)。加えて全てのイベントを使う親コンポーネントはまずありません。そういった背景から子コンポーネント中のあるHTMLElementのイベントの内どれを通知するかの定義を親コンポーネントの側で定義できるようにする需要があります。これは次の様に実現できます。

 使用したVue.jsの機能はコンポーネントや要素を参照するための機能であるrefです。これを用いてテンプレート内のHTMLElementを参照して”親コンポーネントにイベントを通知するイベント”をHTMLElementに直に取り付けます。
API — Vue.js#$refs
 以下コードの抜粋です。

/** 親コンポーネントのtemplate部 */
// 親コンポーネントでは子コンポーネントでemitしてほしいイベント名を配列で渡し
// それぞれのイベントで起きることを定義します
    <div style="display:flex; flex-direction:column">
      <input-wrapper
        label="1つ目"
        :emit-events="['click', 'keydown']"
        @click="msg='1つ目のclickイベント発火'"
        @keydown="msg='1つ目のkeydownイベント発火'"
      />
      <input-wrapper
        label="2つ目"
        :emit-events="['focus', 'change']"
        @focus="msg='2つ目のfocusイベント発火'"
        @change="msg='2つ目のchangeイベント発火'"
      />
      <input-wrapper
        label="3つ目"
        :emit-events="['focus', 'change']"
        @change="msg='3つ目のchangeイベント発火'"
        @focus="msg='3つ目のfocusイベント発火'"
      />
    </div>
  </div>

/** 子コンポーネント */
<template>
  <label>
    {{ label }}
    <!-- refで参照 -->
    <input type="text" ref="inputRef">
  </label>
</template>
<script>
export default {
  props: {
    label: { type: String, default: "" },
    emitEvents: { // emitするイベント名たちを配列で受け取ります。デフォルトでよく使うイベントを入れておくと便利
      type: Array,
      default: () => ["click", "change"]
    }
  },
  mounted() {
    this.emitEvents.forEach(eventName => {
      // addEventLisnerで参照した生のHTMLElementに起きたイベントをそのまま親コンポーネントに流す処理を追加
      this.$refs.inputRef.addEventListener(eventName, originalEvent => {
        this.$emit(eventName, originalEvent);
      });
    });
  }
};
</script>
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG