VueでFizzBuzzを書いてみた

あまり知られていないが、Vueの単一ファイルコンポーネントは自身を再帰的に呼び出すことができる。 この仕組みを利用してFizzBuzzを書いてみた。

コード

ファイル名をFizzBuzz.vueとして、以下のコードを書いた。

テンプレート内で自身を参照する際のコンポーネント名はファイル名と同じになる。 この例ではファイル名がFizzBuzz.vueなので、呼び出すコンポーネントの名前はFizzBuzzになる。

<script setup lang="ts">
type Props = Partial<{ 
  i: number
  n: number 
}>

const { i = 1, n = 100 } = defineProps<Props>()

const value = computed(() => {
  if (i % 15 === 0) return 'FizzBuzz'
  if (i % 3 === 0) return 'Fizz'
  if (i % 5 === 0) return 'Buzz'
  return i.toString()
})
</script>

<template>
  <p v-bind="$attrs">{{ value }}</p>
  <!-- ここで再帰 -->
  <FizzBuzz v-if="i < n" v-bind="$attrs" :i="i + 1" :n="n" />
</template>
FizzBuzz.vue

省略するが、このコンポーネントを配置するとFizzBuzzが出力される。

Tips

コード例では次にあげるちょっとしたテクニックを利用している。

複数ルート要素

かつてVueコンポーネントは単一のルート要素を持つ必要があった(無用な<div>で全体を囲んだりしていた記憶がある方も少なくないだろう)。 最近のVueはこの制約が緩和され、複数のルート要素を持つことができるようになった。

複数ルート要素を持つコンポーネントではコンポーネントに指定した属性値の自動継承が無効になる。 例として、このFizzBuzzのテキストをオシャレな色に変更するため、text-fashionable-colorクラスを適用したいケースを考えよう。

<template>
  <FizzBuzz class="text-fashionable-color" />
</template>

単一ルート要素のコンポーネントの場合は、そのルート要素にコンポーネントの呼び出し側で指定した属性がそのまま設定される。 この機能はAttribute Inheritanceと呼ばれている。

一方で、複数のルート要素を持つコンポーネントではどの要素にclass属性を設定すればよいかを自動で判断せず、警告を発生させる。 この警告を回避するには、v-bind="$attrs"を使っていずれかの要素に明示的に属性を継承させるか、defineOptions()を使ってAttribute Inheritanceを無効にすればよい。

Propsの分割代入

こちらは実験的機能だが、Vue3.3からPropsの分割代入ができるようになった。

Reactive Props Destructure

Propsを分割代入するとリアクティブ性が失われてしまう問題があったが、このアップデートにより解決された。 いちいちprops.hogeみたいな書き方をしなくて良くなるので便利。

Vue3.3時点では、Propsの分割代入を利用するためには明示的な機能の有効化が必要となっている。 詳しい設定方法については上述のリンクとRFCを参照してほしい。

Nuxtでは、nuxt.config.tsに以下のように記述すればOK。

export default defineNuxtConfig({
  vite: {
    vue: { script: { propsDestructure: true } },
  },
  // ......
})
nuxt.config.ts

再帰的Vueコンポーネントの使い所

ネストしたUIを表現するのに使えるかもしれないと思ったが、ネストした表現を実際に使う機会はあまりなさそう。 普通のループ(v-for)を代替することができるが、そうするメリットがあるケースもほとんどなさそう。

再帰が使えるという事実と、ループでは都合の悪い何かを踏んだときに再帰が使えるかもということは覚えておくと役に立つかもしれない。

share with:
この記事をTwitterで共有
この記事をはてなブックマークに追加