🐬

Nuxt3の動的コンポーネントにて、NuxtLinkを使用する

2024/01/22に公開

指定の条件でNuxtLinkspanタグの切り分けをしたいと思い、その時に試したことを共有します。

前提

  • Nuxtのバージョンは3.8.1を使用しています。
  • コンポーネントにて、指定のpropがある場合、NuxtLinkタグ。ない場合はspanタグを使用します。
    • コンポーネントのタグの切り替えをするために使用するpropsはisLinkです。

NuxtLink componentについて

https://nuxt.com/docs/api/components/nuxt-link

Dynamic Componentについて

https://nuxt.com/docs/guide/directory-structure/components#dynamic-components

結論

もし対応コード・原因のみを確認したい場合は、以下のコードをご覧いただくか、GitHub のissuesを参照していただければと思います。

<!-- コンポーネントでの記載 -->
<template>
  <component :is="isLink ? NuxtLink : 'span'">
    何かの要素
  </component>
</template>

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

withDefaults(
  defineProps<{ isLink?: boolean }>(),
  {
    isLink: false,
  },
)
</script>

<!-- 呼び出す際の処理 -->
<!-- NuxtLinkタグの場合 -->
<AnyComponent isLink />
<!-- spanの場合 -->
<AnyComponent /> <!-- or <AnyComponent isLink={false} /> -->

https://github.com/nuxt/nuxt/issues/13659#issuecomment-1596302040

試してみた方法について

1. 通常のHTMLタグと同様に記載してみる

はじめとして、通常のタグと同様の感覚で、NuxtLinkを呼んでみました。
NuxtLinkはNuxt3では auto importされるはずだと思っているため、import NuxtLinkのようなimport文についても記載はしておりません。

<template>
  <component :is="isLink ? 'NuxtLink' : 'span'">
    何かの要素
  </component>
</template>

<script lang="ts" setup>
withDefaults(
  defineProps<{ isLink?: boolean }>(),
  {
    isLink: false,
  },
)
</script>

nuxtlinkのタグとして出力されるため、失敗

1. 通常のHTMLタグと同様に記載してみるでの表示結果 - 失敗

2. resolveComponentを使用して、動的に表示させてみる

次はresolveComponentを使用し、scriptの中で動的コンポーネントとして表示させることでうまくいくか試してみようと思います。
resolveComponentの詳細については以下の記載を参照してください。

https://vuejs.org/api/render-function

<template>
  <component :is="LinkComponent">
    何かの要素
  </component>
</template>

<script lang="ts" setup>
const props = withDefaults(
  defineProps<{ isLink?: boolean }>(),
  {
    isLink: false,
  },
)
const LinkComponent = resolveComponent(props.isLink ? 'NuxtLink' : 'span')
</script>

2. resolveComponentを使用して、動的に表示させてみる - 失敗

参考までにNuxtLinkの部分を文字列ではなくコンポーネントとして行ってみましたが、システムエラー・typeエラーになるため、こちらもうまくいきませんでした。

<template>
  <component :is="LinkComponent">
    何かの要素
  </component>
</template>

<script lang="ts" setup>
const props = withDefaults(
  defineProps<{ isLink?: boolean }>(),
  {
    isLink: false,
  },
)
const LinkComponent = resolveComponent(props.isLink ? NuxtLink : 'span')
</script>

システムエラーの結果について

typeエラーについて

3. NuxtLinkをのimport文を記載してみる

ここまでで、動的コンポーネント周りの設定というよりはNuxtLink周りの設定周りが原因だと思ったため、Nuxt.jsのissuesを探してみたところ、以下のissuesを発見

https://github.com/nuxt/nuxt/issues/13659

issuesを見ていたところ、NuxtLinkはグローバルに登録はされていないというコメントを見つけたので、こちらを一度試してみます。

https://github.com/nuxt/nuxt/issues/13659#issuecomment-1573568006

It's because NuxtLink isn't globally registered - it's auto-imported. Within a Nuxt project we detect resolveComponent('NuxtLink') and rewrite this into an import statement.
You can probably resolve this in your case by adding your ui library to build.transpile, and using import { NuxtLink } from '#imports'

<template>
  <component :is="isLink ? NuxtLink : 'span'">
    何かの要素
  </component>
</template>

<script lang="ts" setup>
import { NuxtLink } from '#imports'
withDefaults(
  defineProps<{ isLink?: boolean }>(),
  {
    isLink: false,
  },
)
</script>

こちら、#importsに以下のエラーが出力されるため、失敗

記載の結果について

4. NuxtLinkcomponentsを呼んで記載してみる

さらにissuesを読み進めていくと

https://github.com/nuxt/nuxt/issues/13659#issuecomment-1596302040

For those wondering, the correct import is import { NuxtLink } from '#components';
Then you can use: <component :is="to ? NuxtLink : 'div'" :to="to">,
Nuxt need better documentation about the Virtual Files, they are helpful, but confusing.

こちら、#componentsに変更してみたところ、うまく実行させることができました。

結論の箇所にも記載していますが、改めてこちらにもコードを記載します。
toなどの他のpropsの値については省略しています。

<template>
  <component :is="isLink ? NuxtLink : 'span'">
    何かの要素
  </component>
</template>

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

withDefaults(
  defineProps<{ isLink?: boolean }>(),
  {
    isLink: false,
  },
)
</script>

記載の結果について

Discussion