404 Not Found

404 Not Found


nginx

Vue 3のスロット:デフォルトのスロット、名前付きスロット、スコープ付きスロット

スロットは、Vueのコンテンツ配信APIの一部です。親コンポーネントは、子コンポーネントの「スロット」に任意のコンテンツを挿入できます。スロットにより、コンポーネントは「テンプレート」と同じくらい柔軟になります。つまり、親コンポーネントが表示内容を制御し、子コンポーネントが表示位置を制御する仕組みです。

Vue 3 では、デフォルトスロット、名前付きスロット、スコープ付きスロットの 3 種類のスロットが用意されています。このレッスンでは、これら 3 種類のスロットの使い方とベストプラクティスを習得できるよう解説します。

1. 学習内容


2. カードコンポーネントの内容をカスタマイズできないという課題

(1) 課題:カードスタイルが5種類あり、5つのコンポーネントを作成する必要がある

アリスのEコマース管理担当者は、5種類のカードを必要としていました:

VUE
<!-- ❌ The "Flip" Version:5 component -->
<!-- ProductCard.vue -->
<div class="card">{{ product.name }}</div>

<!-- UserCard.vue -->
<div class="card">{{ user.name }}</div>

<!-- OrderCard.vue -->
<div class="card">Order #{{ order.id }}</div>

<!-- 5 component,90% The code is the same,The only difference is the content -->

プロダクトマネージャーのチャーリー:

「アリス、来週はあと5種類のカードが必要なんだ。これ以上新しいコンポーネントを追加し続けるわけにはいかない。どんなコンテンツでも対応できる、柔軟性のあるカードが必要なんだ。」

(2) Vueのスロットを用いた解決策:1つのBaseCardで、コンテンツは親コンポーネントから提供される

VUE
<!-- BaseCard.vue - General-Purpose Card,The slot accepts any content -->
<template>
  <div class="card">
    <div v-if="$slots.header" class="card-header">
      <slot name="header" />
    </div>
    <div class="card-body">
      <slot />  <!-- Default Slot -->
    </div>
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>
VUE
<!-- ProductCard Usage BaseCard -->
<BaseCard>
  <template #header>
    <h3>Product</h3>
  </template>
  
  <p>iPhone 15 Pro</p>  <!-- Default Slot -->
  
  <template #footer>
    <button>Add to Cart</button>
  </template>
</BaseCard>

1 BaseCard.vue → 無限の種類のカード。新しいカードを追加するたびに、コンテンツを記述するだけで済み、コンポーネントを変更する必要はありません。

(3) 収益

スロットを使用した後は:


3. デフォルトのスロット

(1) 基本的な使い方

VUE
<!-- BaseCard.vue Child component -->
<template>
  <div class="card">
    <!-- Slot: The parent component can contain any content -->
    <slot />
  </div>
</template>
VUE
<!-- App.vue Parent Component -->
<template>
  <BaseCard>
    <h3>Hello</h3>
    <p>This is the card content</p>
  </BaseCard>
</template>

レンダリング結果

HTML
<div class="card">
  <h3>Hello</h3>
  <p>This is the card content</p>
</div>

(2) デフォルトのコンテンツ(フォールバック)

VUE
<!-- Child component -->
<template>
  <div class="card">
    <slot>
      <!-- Default Content:Display when the parent component does not pass data -->
      <p>No content provided</p>
    </slot>
  </div>
</template>
VUE
<!-- The parent component does not pass content -->
<BaseCard />

<!-- Rendering:<p>No content provided</p> -->

<!-- Passing Content from the Parent Component -->
<BaseCard>
  <p>Custom content</p>
</BaseCard>

<!-- Rendering:<p>Custom content</p>(Override Default)-->

(3) 5つの主なユースケース

シナリオ 用途
カードの内容 <slot />
ボタンのテキスト <slot />
リスト項目 <slot :item="item" />
フォームフィールド <slot />
モーダルボックスの本文 <slot />

4. 名前付きスロット

(1) 名前付きスロットの定義

VUE
<!-- BaseLayout.vue Child component -->
<template>
  <div class="layout">
    <header>
      <slot name="header" />
    </header>
    <main>
      <slot />  <!-- Default Slot(Optional name="default") -->
    </main>
    <footer>
      <slot name="footer" />
    </footer>
  </div>
</template>

(2) 親コンポーネントの使用(3つの構文)

VUE
<!-- App.vue Parent Component -->

<!-- Writing Style 1:v-slot:name(Most Comprehensive) -->
<BaseLayout>
  <template v-slot:header>
    <h1>My App</h1>
  </template>
  
  <template v-slot:default>
    <p>Main content</p>
  </template>
  
  <template v-slot:footer>
    <p>Footer</p>
  </template>
</BaseLayout>

<!-- Writing Style 2:Abbreviation #name(Recommendations) -->
<BaseLayout>
  <template #header>
    <h1>My App</h1>
  </template>
  
  <p>Main content</p>  <!-- The default slot can be omitted template -->
  
  <template #footer>
    <p>Footer</p>
  </template>
</BaseLayout>

<!-- Writing Style 3:v-slot Receive multiple slot objects(Vue 3 Recommendations) -->
<BaseLayout>
  <template #header>
    <h1>My App</h1>
  </template>
  
  <template #default="{ user }">  <!-- Deconstructing Scope Slots -->
    <p>Hello, {{ user.name }}</p>
  </template>
  
  <template #footer>
    <button>Logout</button>
  </template>
</BaseLayout>

(3) 動的なスロット名

VUE
<!-- Child component -->
<template>
  <div>
    <slot :name="dynamicSlotName" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header')
</script>

<!-- Parent Component:Dynamic Slot Binding -->
<BaseLayout>
  <template #[dynamicSlotName]>
    <p>Dynamic content</p>
  </template>
</BaseLayout>

(4) スロットの有無を確認する

VUE
<!-- Child component:Check whether data is being received through the slot -->
<template>
  <div class="card">
    <div v-if="$slots.header" class="card-header">
      <slot name="header" />
    </div>
    <slot />
  </div>
</template>
VUE
<!-- Parent Component -->
<BaseCard>
  <template #header>...</template>  <!-- Sent header Slot, Display -->
</BaseCard>

<BaseCard>
  <!-- No header slot passed, Do not display card-header -->
</BaseCard>

5. スコープスロット

(1) 要点:子コンポーネントは、親コンポーネントのスロットにデータを渡す

VUE
<!-- Child component:UserList.vue -->
<template>
  <ul>
    <li v-for="user in users" :key="user.id">
      <!-- Pass user to the parent component's slot -->
      <slot :user="user" :index="index" />
    </li>
  </ul>
</template>

<script setup>
defineProps({ users: Array })
</script>
VUE
<!-- Parent Component:App.vue -->
<template>
  <UserList :users="users">
    <!-- Receive data passed from child components -->
    <template #default="{ user, index }">
      <p>{{ index + 1 }}. {{ user.name }} ({{ user.email }})</p>
    </template>
  </UserList>
</template>

レンダリング結果

HTML
<ul>
  <li><p>1. Alice (alice@example.com)</p></li>
  <li><p>2. Bob (bob@example.com)</p></li>
  <li><p>3. Charlie (charlie@example.com)</p></li>
</ul>

(2) 5つのスコープスロットの配置パターン

パターン 親コンポーネントの実装 目的
すべてのプロップを受け入れる <template #default="slotProps"> オブジェクト全体を受け入れる
脱構築 <template #default="{ user }"> 必要なフィールドのみを使用する
名前の変更 <template #default="{ user: u }"> 変数の競合を避ける
デフォルト値 <template #default="{ user = defaultUser }"> デコンストラクション時にデフォルト値を指定する
テンプレートなし {{ slotProps.user.name }} シンプルなシナリオ(デフォルトのスロット)

(3) 完全な例:カスタマイズ可能なリスト

VUE
<!-- UserList.vue Child component -->
<template>
  <ul class="user-list">
    <li v-for="(user, index) in users" :key="user.id">
      <slot :user="user" :index="index" :isAdmin="user.role === 'admin'" />
    </li>
  </ul>
</template>

<script setup>
defineProps({ users: Array })
</script>
VUE
<!-- Using Parent Components(3 One way) -->
<template>
  <!-- Method 1:A Brief Overview -->
  <UserList :users="users">
    <template #default="{ user }">
      {{ user.name }}
    </template>
  </UserList>
  
  <!-- Method 2:Table Display -->
  <UserList :users="users">
    <template #default="{ user, index, isAdmin }">
      <tr>
        <td>{{ index + 1 }}</td>
        <td>{{ user.name }}</td>
        <td>{{ user.email }}</td>
        <td>
          <span v-if="isAdmin" class="badge admin">Admin</span>
          <span v-else class="badge user">User</span>
        </td>
      </tr>
    </template>
  </UserList>
  
  <!-- Method 3:Card Display -->
  <UserList :users="users">
    <template #default="{ user }">
      <UserCard :user="user" />
    </template>
  </UserList>
</template>

6. $slots と useSlots

(1) $slots スロットへのアクセス

VUE
<!-- Child component -->
<template>
  <div>
    <!-- List all incoming slot names -->
    <div v-for="(_, name) in $slots" :key="name">
      <slot :name="name" />
    </div>
  </div>
</template>

(2) useSlots(Composition API)

VUE
<script setup>
import { useSlots } from 'vue'

const slots = useSlots()

// Check if the slot exists
if (slots.header) {
  console.log('Header slot exists')
}

// Dynamic Rendering
if (slots.default) {
  console.log('Default slot exists')
}
</script>

(3) $slots 対 useSlots

寸法 $slots useSlots()
APIの種類 テンプレート内で直接使用 JS(Composition API)で使用
戻る スロットオブジェクト スロットオブジェクト(refs形式)
シーン テンプレート内 <script setup>

7. 完全な例:カスタマイズ可能なカードコンポーネント

(1) ▶ サンプル:. BaseCard.vue(デフォルト+名前付きスロット)

VUE
<!-- src/components/BaseCard.vue -->
<template>
  <div class="card">
    <div v-if="$slots.header" class="card-header">
      <slot name="header" />
    </div>
    <div class="card-body">
      <slot>
        <!-- Default Content:Display when the parent component does not pass data -->
        <p>No content</p>
      </slot>
    </div>
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>

<style scoped>
.card {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}
.card-header, .card-footer {
  background: #f9fafb;
  padding: 1rem;
}
.card-body {
  padding: 1rem;
}
</style>
▶ 試してみよう

(2) ▶ サンプル:. BaseCardの5つの活用方法

VUE
<!-- 1. The simplest(Transmit Only body) -->
<BaseCard>
  <p>Simple content</p>
</BaseCard>

<!-- 2. With header -->
<BaseCard>
  <template #header>
    <h3>Product Name</h3>
  </template>
  <p>Product description</p>
</BaseCard>

<!-- 3. Complete header + body + footer -->
<BaseCard>
  <template #header>
    <h3>Order #12345</h3>
  </template>
  <p>Total: $99.99</p>
  <template #footer>
    <button>View Details</button>
  </template>
</BaseCard>

<!-- 4. Complete Syntax(v-slot) -->
<BaseCard>
  <template v-slot:header>
    <h3>Header</h3>
  </template>
  <template v-slot:default>
    <p>Body</p>
  </template>
  <template v-slot:footer>
    <p>Footer</p>
  </template>
</BaseCard>

<!-- 5. Scope Slot(Receive Data) -->
<UserCard :user="user">
  <template #actions="{ user }">
    <button @click="edit(user)">Edit</button>
    <button @click="remove(user)">Delete</button>
  </template>
</UserCard>
▶ 試してみよう

(3) ▶ サンプル:. スコープスロットの完全な例

VUE
<!-- Child component:DataTable.vue -->
<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">{{ col.label }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, index) in data" :key="row.id">
        <td v-for="col in columns" :key="col.key">
          <!-- Scope Slot:Allow the parent component to customize cell rendering -->
          <slot :name="`cell-${col.key}`" :row="row" :index="index" :value="row[col.key]">
            <!-- Default:Show Original Values -->
            {{ row[col.key] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script setup>
defineProps({
  columns: { type: Array, required: true },
  data: { type: Array, required: true }
})
</script>
▶ 試してみよう
VUE
<!-- Using Parent Components DataTable -->
<DataTable :columns="columns" :data="users">
  <!-- Custom status column -->
  <template #cell-status="{ row }">
    <span :class="['badge', row.status]">{{ row.status }}</span>
  </template>
  
  <!-- Custom actions column -->
  <template #cell-actions="{ row }">
    <button @click="edit(row)">Edit</button>
  </template>
</DataTable>

(4) ▶ サンプル:. よくある5つの間違いのクイックリファレンス

エラー 症状 解決策
スロット名のスペルミス コンテンツが表示されない <slot name="...">#name が一致しているか確認してください
親コンポーネント内に複数のルート要素が存在します 警告 親コンポーネントが <template #name> で囲まれています
v-slot(旧構文) Vue 2 対応 代わりに v-slot:header または #header を使用してください
デフォルトのスロットが渡されていない 親コンポーネントが表示されていない 親コンポーネント内の <Component /> の内容を確認してください
スコープスロットはデストラクチャリングされない テンプレートは冗長である { user, index } によるデストラクチャリング

(5) ▶ サンプル:. 5 性能比較

構文 コンパイル パフォーマンス 推奨事項
<slot /> 静的 ⭐⭐⭐⭐⭐ デフォルト
<slot name="x" /> 静的 ⭐⭐⭐⭐⭐ 命名済み
<slot :x="x" /> 静的 ⭐⭐⭐⭐ スコープ
v-if="$slots.x" 状態 ⭐⭐⭐⭐ 検出済み
useSlots() 実行時間 ⭐⭐⭐ JSで使用

(6) ▶ サンプル:. $slots の主な5つの用途

VUE
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>

<template>
  <div class="wrapper">
    <!-- 1. Check if the slot exists -->
    <div v-if="slots.header">Has header</div>
    
    <!-- 2. List all slot names -->
    <div v-for="name in Object.keys(slots)" :key="name">
      <slot :name="name" />
    </div>
    
    <!-- 3. Passthrough attribute -->
    <input v-bind="attrs" />
    
    <!-- 4. Conditional Rendering -->
    <template v-if="slots.default">
      <div class="content">
        <slot />
      </div>
    </template>
    
    <!-- 5. Default Slot Packaging -->
    <div class="default-wrapper">
      <slot>
        <p>Default fallback</p>
      </slot>
    </div>
  </div>
</template>
▶ 試してみよう

❓ よくある質問

Q スロットとプロップの違いは何ですか?
A プロップはデータ(任意の JavaScript 値)を渡すのに対し、スロットはコンテンツ(HTML やコンポーネントの断片)を渡します。プロップは設定オプションに適しており、スロットはコンテンツのカスタマイズに適しています。
Q スコープスロットはどのような場合に使用すべきですか?
A 親コンポーネントが、子コンポーネントからのデータに基づいてレンダリングをカスタマイズする必要がある場合です。例えば、テーブルの列のレンダリング、リスト項目のレンダリング、またはカードコンテンツのレイアウトなどです。
Q v-slot# の省略形にはどのような違いがありますか?
A これらは完全に同等です。#header is a shorthand for v-slot:header。省略形の使用をお勧めします(より簡潔だからです)。
Q デフォルトのスロットに名前を付けることはできますか?
A デフォルトのスロットに名前を付ける必要はありません。デフォルトは <slot /> です。どうしても名前を付けなければならない場合は、<slot name="default" /> を使用してください。親コンポーネントでは <template #default> を使用するか、単にコンテンツを直接渡すことができます。
Q スロットをプロップとして渡すことはできますか?
A スロットを直接プロップとして渡すことはできません。ただし、VNode やレンダリング関数をプロップとして渡すことは可能です(上級者向け)。ほとんどの場合は、単に <slot> を使用してください。
Q 親コンポーネント内で子コンポーネントのスロットを検出するにはどうすればよいですか?
A $slots(テンプレート)または useSlots()(JS)を使用します。子コンポーネントは <slot> を使用してスロットを公開し、親コンポーネントは <template #name> を使用してそのスロットに値を設定します。
Q 名前付きスロットにデフォルトの内容を設定できますか?
A はい。<slot name="header"><h3>Default Header</h3></slot>。これは、親コンポーネントがヘッダーを渡さない場合に表示されます。

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

簡単なButtonコンポーネントを実装します:

(2) 上級問題(難易度:⭐⭐)

BaseLayout コンポーネントを実装します:

(3) チャレンジ問題(難易度:⭐⭐⭐)

完全なDataTableコンポーネントシステムを実装する:

  1. DataTable.vue:列とデータを受け取り、テーブルを表示する
  2. スコープスロット:#cell-{key} 親コンポーネントがセルをカスタマイズできるようにします
  3. デフォルトのスロット:親コンポーネントが値を指定していない場合に、元の値を表示します
  4. 親コンポーネントの例:ユーザーテーブルと注文テーブル――2つの異なる列のレンダリング方法
  5. パフォーマンス:v-memoは行をキャッシュします(データに変更がない場合は再描画をスキップします)
  6. TypeScriptの厳格な型付け
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%