Vue.jsのスロット(slot)の使い方:完全ガイド

2025-07-30

はじめに

Vue.jsのスロット(slot)は、コンポーネント間でコンテンツを配布するための強力なメカニズムです。この機能を理解することで、より柔軟で再利用可能なコンポーネントを作成できるようになります。本記事では、スロットの基本概念から応用パターンまで、詳細に解説していきます。

スロットの基本概念

スロットとは何か?

スロットはVue.jsのコンポーネントシステムにおける「コンテンツ配布API」です。親コンポーネントから子コンポーネントにテンプレートの一部を渡すことができます。これにより、コンポーネントの外観や一部の振る舞いをカスタマイズしながら、コア機能を再利用できます。

なぜスロットが必要なのか?

スロットが解決する主な問題点は以下の通りです:

  1. コンポーネントの柔軟性向上:固定されたテンプレートだけでなく、動的なコンテンツを受け入れることが可能
  2. 関心の分離:コンポーネントのロジックと表示を分離できる
  3. 再利用性の向上:1つのコンポーネントを様々なコンテキストで使用可能

基本的なスロットの使い方

デフォルトスロット

最もシンプルなスロットの形です。子コンポーネントで<slot></slot>を定義し、親コンポーネントからコンテンツを渡します。

子コンポーネント (ChildComponent.vue):

<template>
  <div class="child">
      <h2>子コンポーネントのタイトル</h2>
      <slot></slot>
  </div>
</template>

親コンポーネント:

<template>
  <ChildComponent>
    <p>これはスロットに渡されるコンテンツです</p>
  </ChildComponent>
</template>

この場合、<p>タグの内容が子コンポーネントの<slot>の位置に挿入されます。

フォールバックコンテンツ

スロットにはデフォルトのコンテンツ(フォールバック)を設定できます。親からコンテンツが渡されなかった場合に表示されます。

子コンポーネント:

<template>
  <div class="child">
    <slot>
      <p>デフォルトのコンテンツです</p>
    </slot>
  </div>
</template>

名前付きスロット

複数のスロットを使用したい場合、名前付きスロットが便利です。

基本的な名前付きスロット

子コンポーネント:

<template>
  <div class="child">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot><!-- デフォルトスロット --></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

親コンポーネント:

<template>
  <ChildComponent>
    <template v-slot:header>
      <h1>カスタムヘッダー</h1>
    </template>

    <p>デフォルトスロットのコンテンツ</p>

    <template v-slot:footer>
      <p>カスタムフッター</p>
    </template>
  </ChildComponent>
</template>

v-slotの省略記法

Vue 2.6.0以降では、v-slot:#に省略できます。

<template>
  <ChildComponent>
    <template #header>
      <h1>カスタムヘッダー</h1>
    </template>
    ...
  </ChildComponent>
</template>

スコープ付きスロット

スロットの高度な機能として、子コンポーネントのデータを親コンポーネントのスロットコンテンツで使用できる「スコープ付きスロット」があります。

基本的なスコープ付きスロット

子コンポーネント:

<template>
  <div>
    <slot :user="user" :age="age"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: '山田太郎',
      age: 30
    }
  }
}
</script>

親コンポーネント:

<template>
  <ChildComponent>
    <template v-slot:default="slotProps">
      <p>ユーザー名: {{ slotProps.user }}</p>
      <p>年齢: {{ slotProps.age }}</p>
    </template>
  </ChildComponent>
</template>

オブジェクトの分割代入

ES6の分割代入を使用して、より簡潔に書けます。

<template>
  <ChildComponent>
    <template v-slot:default="{ user, age }">
      <p>ユーザー名: {{ user }}</p>
      <p>年齢: {{ age }}</p>
    </template>
  </ChildComponent>
</template>

名前付きスコープ付きスロット

名前付きスロットとスコープ付きスロットを組み合わせることも可能です。

子コンポーネント:

<template>
  <div>
    <slot name="user" :user="user"></slot>
    <slot name="age" :age="age"></slot>
  </div>
</template>

親コンポーネント:

<template>
  <ChildComponent>
    <template #user="{ user }">
      <p>ユーザー: {{ user }}</p>
    </template>
    <template #age="{ age }">
      <p>年齢: {{ age }}</p>
    </template>
  </ChildComponent>
</template>

動的スロット名

動的な名前でスロットを指定することも可能です。

<template>
  <ChildComponent>
    <template v-slot:[dynamicSlotName]>
      ...
    </template>
  </ChildComponent>
</template>

<script>
export default {
  data() {
    return {
      dynamicSlotName: 'header'
    }
  }
}
</script>

スロットの実践的な使用例

汎用レイアウトコンポーネント

スロットを使用して、汎用的なレイアウトコンポーネントを作成できます。

LayoutComponent.vue:

<template>
  <div class="layout">
    <header class="header">
      <slot name="header"></slot>
    </header>
    <aside class="sidebar">
      <slot name="sidebar"></slot>
    </aside>
    <main class="content">
      <slot></slot>
    </main>
    <footer class="footer">
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<style scoped>
.layout {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar content"
    "footer footer";
  grid-template-columns: 200px 1fr;
  gap: 20px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }
</style>

使用例:

<template>
  <LayoutComponent>
    <template #header>
      <h1>アプリケーションタイトル</h1>
    </template>
    <template #sidebar>
      <nav>
        <ul>
          <li>メニュー1</li>
          <li>メニュー2</li>
        </ul>
      </nav>
    </template>

    <p>メインコンテンツ</p>

    <template #footer>
      <p>© 2023 会社名</p>
    </template>
  </LayoutComponent>
</template>

リストレンダリングコンポーネント

スコープ付きスロットを使用して、データのレンダリング方法をカスタマイズ可能なリストコンポーネントを作成できます。

ListComponent.vue:

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <slot :item="item" :index="index"></slot>
    </li>
  </ul>
</template>

<script>
export default {
  props: {
    items: {
      type: Array,
      required: true
    }
  }
}
</script>

使用例:

<template>
  <ListComponent :items="users">
    <template v-slot="{ item, index }">
      <span>{{ index + 1 }}. {{ item.name }} ({{ item.age }}歳)</span>
    </template>
  </ListComponent>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { name: '山田太郎', age: 30 },
        { name: '佐藤花子', age: 25 }
      ]
    }
  }
}
</script>

スロットの高度なパターン

スロットの受け渡し

コンポーネントがスロットを別のコンポーネントに渡す「スロットの受け渡し」パターンがあります。

WrapperComponent.vue:

<template>
  <div class="wrapper">
    <ChildComponent>
      <!-- すべてのスロットをChildComponentに渡す -->
      <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
        <slot :name="name" v-bind="slotData"></slot>
      </template>
    </ChildComponent>
  </div>
</template>

レンダーレスコンポーネント

ロジックのみを持ち、レンダリングを完全にスロットに委譲する「レンダーレスコンポーネント」パターンがあります。

ToggleComponent.vue:

<template>
  <slot :isOn="isOn" :toggle="toggle"></slot>
</template>

<script>
export default {
  data() {
    return {
      isOn: false
    }
  },
  methods: {
    toggle() {
      this.isOn = !this.isOn
    }
  }
}
</script>

使用例:

<template>
  <ToggleComponent v-slot="{ isOn, toggle }">
    <button @click="toggle">
      {{ isOn ? 'ON' : 'OFF' }}
    </button>
  </ToggleComponent>
</template>

スロットに関する注意点とベストプラクティス

注意点

  1. スロットコンテンツのスコープ:スロットコンテンツは親コンポーネントのスコープでコンパイルされるため、親のデータにアクセスできますが、子のデータにはアクセスできません(スコープ付きスロットを除く)。
  2. $slotsと$scopedSlots$slotsは静的なスロットコンテンツにアクセスし、$scopedSlotsはスコープ付きスロットにアクセスします。
  3. v-ifとの併用:スロットコンテンツ内でv-ifを使用する場合、条件によってはスロットが全くレンダリングされない可能性があります。

ベストプラクティス

  1. 明確なインターフェース設計:スロットを使用するコンポーネントは、どのようなスロットが利用可能で、どのようなプロパティが利用できるかを明確にドキュメント化しましょう。
  2. 適切なフォールバックコンテンツ:必須でないスロットには、適切なデフォルトコンテンツを提供しましょう。
  3. スコープ付きスロットの活用:コンポーネントのデータやメソッドを親コンポーネントと共有する必要がある場合、スコープ付きスロットを検討しましょう。
  4. パフォーマンス考慮:多くのスロットを持つコンポーネントは、パフォーマンスに影響を与える可能性があるため、必要以上に複雑にしないようにしましょう。

まとめ

Vue.jsのスロットは、コンポーネントの再利用性と柔軟性を大幅に向上させる強力な機能です。基本的なスロットから名前付きスロット、スコープ付きスロットまで、様々なユースケースに対応できます。適切に使用することで、よりメンテナンス性の高いコンポーネントアーキテクチャを構築できるでしょう。

スロットをマスターすることは、Vue.jsのコンポーネントシステムを深く理解する上で重要なステップです。実際のプロジェクトで積極的に活用し、その利便性を実感してください。