🌵

動的コンポーネントで@clickが動かないときは@click.nativeを使う

2021/11/06に公開

業務でNuxtを使っていて、うまく@clickが発火しない現象に遭遇したのでメモ。

環境

  • macOS Big Sur 11.5.2
  • nuxt@2.15.2

やっていることとしては、動的コンポーネントを利用して、

  • linkプロパティが渡されていればaタグとして出力する
  • linkプロパティが渡されていなければbuttonタグとして出力する

というものです。buttonとして使いたいときはメソッドを呼び出して処理を行わせたいのですが、@clickを設定してもうまく発火してくれません。

   <!--@clickだとsampleメソッドが呼び出されない-->
   <dynamic-component-sample
   @click="sample"
   >Button</dynamic-component-sample>

dynamic-component-sampelは以下のようなコンポーネントを定義しています。

DynamicComponentSample.vue
<template>
   <div class="border-2 bg-gray-200 m-3 p-3">
      <component
         :is="type"
         :href="link"
      >
      <slot />
      </component>
   </div>
</template>

<script>
export default {
   props: {
      link: String,
      default: null
   },
   computed: {
      type(){
         if(this.link){
            return 'a';
         } else {
            return 'button'
         }
      }
   }
}
</script>

対応策

結論としては、.native修飾子を使うことで解決できました。

    <!--@clickではなく、@click.nativeにする-->
    <dynamic-component-sample
      @click.native="sample"
    >Button</dynamic-component-sample>

.nativeってなに?

Vue.jsの公式ドキュメントを漁ってみると、以下のような説明がありました。

.native - listen for a native event on the root element of component.

コンポーネントのルート要素上のネイティブイベントをリッスンするといったもののようです。
通常のbuttonなどのネイティブなHTML要素でイベントハンドリングしたいときはv-onディレクティブを使えばいいですが、
自分で作成したコンポーネントでabuttoninputなどのネイティブイベントを使いたい場合はさらに.native修飾子をつける必要があります。

なお、公式の以下のページだと、コンポーネント定義時に$listenersプロパティを使って、buttoninputと同じように.nativeを使わずにすむ
コンポーネントの作成方法が記載されていますので、そうしたい方には参考になりそうです。

https://vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components

ソース全体

DynamicComponentSample.vue
<template>
   <div class="border-2 bg-gray-200 m-3 p-3">
      <component
         :is="type"
         :href="link"
      >
      <slot />
      </component>
   </div>
</template>

<script>
export default {
   props: {
      link: String,
      default: null
   },
   computed: {
      type(){
         if(this.link){
            return 'a';
         } else {
            return 'button'
         }
      }
   }
}
</script>
index.vue
<template>
  <div class="container">
    <dynamic-component-sample
      :link="'https://google.com'"
    >aタグ</dynamic-component-sample>
    <br>
    <!--@clickではなく、@click.nativeにする-->
    <dynamic-component-sample
      @click="sample"
    >Button</dynamic-component-sample>
  </div>
</template>

<script>
import DynamicComponentSample from '~/components/DynamicComponentSample.vue';

export default {
  components: { DynamicComponentSample },
  data() {
    return { test: process.env.NODE_ENV };
  },
  methods: {
    sample(){
      window.alert('ボタンタグを押したよ')
    }
  }
};
</script>

参考

Discussion