v-for 内でコンポーネント、クラス名、イベントハンドラまで動的に指定する

ROXX エンジニアの匠平(@show60)です。

同じようなタグが繰り返されているのを見ると、どうにかスッキリまとめられないもんかなと思いますよね。

今回はアイコンコンポーネントを例に、 v-for 内で動的な指定と、クラス名、イベントハンドラの設定までやってみようと思います。

v-for のループ内で任意のカスタムタグを指定する

ユースケースとしては、バラバラのコンポーネントを並べたいときなどでしょう。

1 つのアイコンのみを含んだコンポーネントがあるとして、並びに規則性のある場合には 1 つずつタグを置いてスタイルを設定するよりもコード量が減ってスッキリします。

下記のコードを例にすると、各アイコンコンポーネントが並ぶとき、コードの見通しはいいのですが同じクラス名が入ってくると冗長になりがちなのですっきりとさせたい気持ちです。

<icon-news-feed class="icon"/>
<icon-my-page class="icon"/>
<icon-settings class="icon"/>

配列の要素名と同じアイコンを指定する

下記のようなアイコンの名称が入った配列を用意します。

// 並べたいアイコン
icons: [
  'news-feed',
  'my-page',
  'settings'
]

テンプレートでこのように記述します。

// template
<ul>
  <li
    v-for="(icon, index) in icons"
    :key="index"
  >
    <component 
      :is="icon-${icon}"
    />
  </li>
</ul>

Vue の is 属性を使うことでコンポーネントを動的に生成できるため、単純な v-for だけで表現できます。

注意が必要なのは、当然ですがこれらのアイコンコンポーネントの import は個別に必要ということです。

ここでは 3 つのコンポーネントを例にしていますが、もっとアイコンが増えると利用価値も十分あるかと思います。

また、このアイコンを並べるコンポーネントを用意し、並べたいアイコン名を配列にして親コンポーネントから props で渡すだけで指定することができます。

API — Vue.js

component タグにクラス名を追加すると、呼ばれているアイコンコンポーネントにクラス名が追加されます。こちらも動的に指定できますので例を挙げてみます。

クラス名を動的に指定

ナビバーやフッターなどでは、上記のようにアイコンを並べることがあるかと思いますが、現在のページのアイコンのみ色を変えたいですね。その場合は、動的にクラス名を指定すればよさそうです。

// template
<ul>
  <li
    v-for="(icon, index) in icons"
    :key="index"
  >
    <component 
      :class="getClass(icon)"
      :is="icon-${icon}"
    />
  </li>
</ul>

...

<script>
// import で各アイコンコンポーネントを呼び出し、 export 内で components に記述する

export default {
  methods: {
    getClass(iconName) {
      const path = this.$route.path
      if (iconName === path) {
        return 'is_selected'
      }
    }
  }
}
</script>

// style で is_selected に任意のスタイルを当てる

v-for のループ内で任意のイベントハンドラを指定する

Vue.js 2.6 からディレクティブの引数を動的に指定できるようになりました。

テンプレート構文 — Vue.js

要するに v-bind:●●="something" , v-on:●●="something()" の●●を動的に指定できるのですが、ユースケースとしてはあまり多くないのかなという印象です。

上記で挙げている例でいうと、ある条件下ではアイコンの click 時にイベントが発火し、別の条件下では mouseover でイベント発火するようなケースですね。サイドメニューのサブメニューの開閉を切り分けたいときなんかには使えそうな印象です。

他には下記のように、アイコンが示すページ内にいるときにはイベントハンドラを与えないという処理には使えそうです。

// template
<ul>
  <li
    v-for="(icon, index) in icons"
    :key="index"
    @[getEvent(icon)]="handleEvent()"
  >
    <component 
      :is="icon-${icon}"
    />
  </li>
</ul>

...

<script>
export default {
  methods: {
    getEvent(iconName) {
      const path = this.$route.path
      // 今開いているページのアイコンにクリックイベントを与えない
      if (iconName !== path) {
        return 'click'
      }
    },
    handleEvent() {
      console.log('何かを発火!!')
    }
  }
}
</script>

叩いたメソッドの handleEvent 内で分岐することが多いかもしれませんが、このように書くこともできそう、という所感です。

PHPStorm と VSCode のデフォルト設定では、イベント名 ( handleEvent() のところ) にシンタックスハイライトが効かないので、コードの読みやすさの面ではあまり良くないですね。

この用途ではコード量は減らないため、別のイベントハンドラを与えたいときに使う、が有用のようです。

動的引数の値の成約

ドキュメント内にもありますが、動的引数の指定にはいくつか制約があります。上記のケースで、 method で @[getEvent(icon)] のように指定せず直接記述したいと思いましたが、下記のような記述はできません。

@[ ] 内には演算子は書けないようです。

// 不正な記述
@[$route.path !== icon ? 'click' : '' ]="doSomething()"

例えば icon: {event: 'click'} のようなオブジェクトが渡ってきたときに、 icon.eventclick を指定したいところですが、こちらも同様に記述できません。そのため上記のように method で与えてあげる必要があります。

// 不正な記述
@[icon.event]="doSomething()"

制約が多いため用途は限定的かもしれません。

動的引数の式には構文上の制約があります。というのも、スペースや引用符のような一部の文字は、HTML の属性名としては不正な文字だからです。また、in-DOM テンプレートを使う場合は、大文字のキーも避ける必要があります。 テンプレート構文 — Vue.js

さいごに

コンポーネントからクラス名、イベントハンドラまでを動的に配置してみましたが、イベントハンドラについては用途が限られる印象なので、開発の大きな助けになるイメージではなさそうでした。良い事例があれば教えていただけるとありがたいです。

最後に、弊社 ROXX では自社プロダクト一緒に成長させていくエンジニアを募集しています!

ご興味をお持ちの方はぜひご連絡ください!

www.wantedly.com

www.wantedly.com