component :is で動的にタグを作れる

掲載日
更新日

はじめに

サイトで使用しているコンポーネントで、hタグをあるページではh2、あるページではh3で出す、ということがしたくなりました。

素直に考えるとv-ifを使った以下のような方法になるかなと思いますが、ちょっと冗長だし、hタグのレベルを増やしたくなったら都度追加しないといけなくなってしまいます。

<script setup lang="ts">
interface Props {
    hLevel?: number
}

withDefaults(defineProps<Props>(), {
    hLevel: 2,
})
</script>

<template>
    <h2 v-if="hLevel==2">
    	H2
    </h2>
    <h3 v-if="hLevel==3">
    	H3
    </h3>
    ...
</template>

解決方法

「component」コンポーネント(ややこしい)を使用すれば、以下のように書けます。

<script setup lang="ts">
interface Props {
    hLevel?: number
}

withDefaults(defineProps<Props>(), {
    hLevel: 2,
})
</script>

<template>
    <component :is="'h' + hLevel">
    	Propsに持たせたhLevelに応じてhタグのレベルを動的に変えられる!
    </component>
</template>

Built-in Special Element というやつのようです。templateとslotは馴染みがありますが、componentもそのお仲間。

コンポーネントを指定することも出来る。

上記リンク先にある通り、componentはisにstringだけでなくComponentも使えます。

interface DynamicComponentProps {
  is: string | Component
}

ページャーのような「アクティブなリンクはspanタグにして、他のリンクはNuxtLinkにしたいみたいな時も使えます。」(importは必要)

v-bindを使ってattributeも動的に展開できるので、タグの状態によってclassを変えたり、以下の例のようにNuxtリンクの場合はto(リンク先のパス)を渡すといったことも可能です。

<script setup lang="ts">
import { NuxtLink } from "#components";

const nowPageNo = 2;
const pageNoList = [1, 2, 3, 4];

const linkOrSpanComponent = (pageNo: number) => {
  if (pageNo == nowPageNo) {
    return "span";
  }
  return NuxtLink;
};

const linkOrSpanProps = (pageNo: number) => {
  if (pageNo == nowPageNo) {
    return {
      class: "active",
    };
  }

  return {
    class: "text-blue-700",
    to: "#",
  };
};
</script>

<template>
  <div class="max-w-xl m-auto">
    <ul class="p-8 flex gap-4 font-bold">
      <li v-for="pageNo in pageNoList">
        <component :is="linkOrSpanComponent(pageNo)" v-bind="linkOrSpanProps(pageNo)">
          {{ pageNo }}
        </component>
      </li>
    </ul>
  </div>
</template>

上記コードを開発者ツールで見ると添付のスクリーンショットのようになります。

NuxtLinkコンポーネントとspanタグが出し分けされていることが分かるスクリーンショット。

以下のように、現在いるページの番号だけspanタグになり、他はNuxtLink(aタグ)になったことが分かります。

<ul class="p-8 flex gap-4 font-bold">
    <li><a href="#" class="text-blue-700">1</a></li>
    <li><span class="active">2</span></li>
    <li><a href="#" class="text-blue-700">3</a></li>
    <li><a href="#" class="text-blue-700">4</a></li>
</ul>

おわりに

「conmponent :is」を使うことで、何らかの条件でタグを出し分けしたい時にv-ifを使った冗長な処理を簡潔に書けるようになりました。

とはいえ、attributeも変わるようなレベルの時はその量にもよると思いますが、さすがに関数に分けて書かないとタグの中がごちゃごちゃで却って見にくくなり、関数を分けると冗長になり…ということになるので、
ここまで来たらさすがにv-ifでもいいんじゃないかという気もするので、ケースバイケースですね。
(spanとaタグの出し分けなら、hタグのようにレベルが増える心配もないと思うので。)

記事の作成者のA.W.のアイコン

この記事を書いた人

A.W.
茨城県在住Webエンジニアです。 PHPなどを業務で使用しています。 趣味ではGoやNuxt、Flutterをやってます。

Comment