採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

Vue — Slot

テクノロジ系 / L02 component-design — コンポーネント設計

Vue — Slot

デフォルト slot

<!-- Card.vue -->
<template>
  <div class="card">
    <slot></slot>
  </div>
</template>
<!-- 使う側 -->
<Card>
  <h2>Title</h2>
  <p>Content</p>
</Card>

<slot></slot> の位置に親が渡した中身が入る。

React の children に相当。

デフォルト値

<!-- Card.vue -->
<template>
  <div class="card">
    <slot>Default content</slot>
  </div>
</template>

→ slot が空のときは "Default content" が出る。

Named Slot

複数の slot 位置を用意:

<!-- Layout.vue -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
<!-- 使う側 -->
<Layout>
  <template #header>
    <h1>Site Title</h1>
  </template>

  <p>Main content</p>      <!-- default slot -->

  <template #footer>
    <p>© 2026</p>
  </template>
</Layout>

#name (= v-slot:name) で対象を指定。

Scoped Slot — slot に値を渡す

子から親に値を渡したい:

<!-- ItemList.vue -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item" :index="i" />
    </li>
  </ul>
</template>
<!-- 使う側 -->
<ItemList :items="users">
  <template #default="{ item, index }">
    {{ index + 1 }}. {{ item.name }}
  </template>
</ItemList>

React の Render Props に相当。

子は構造を担当、親は見た目を決める 分業。

動的 slot 名

<template>
  <BaseLayout>
    <template #[slotName]>
      Content
    </template>
  </BaseLayout>
</template>

#[slotName] で動的指定。

高度パターン、普段は使わない。

React との対比

ReactVue
中身渡しchildrendefault slot
複数slot複数 propsnamed slot
値を渡すRender Props (関数)Scoped Slot

Vue の slot は 宣言的、React は 関数渡し

読みやすさはほぼ同等。

まとめ

  • <slot></slot> でデフォルト中身渡し
  • <slot name="x"> + <template #x> で複数 slot
  • Scoped Slot で子から値を渡す
  • 動的 slot 名は #[name]
  • React の children / Render Props に対応

最後は provide / inject

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

Lv1: 基本 — デフォルト slot

問題

<!-- Card.vue -->
<template>
  <div class="card">
    <slot>Default content</slot>
  </div>
</template>

<!-- 親 -->
<Card>
  <h2>Title</h2>
  <p>Body</p>
</Card>

<Card />   <!-- slot 空 → "Default content" 表示 -->

合格基準

  • slot に複数要素を渡せる
  • 空のときデフォルト

Lv2: 応用 — Named slot

問題

<!-- Layout.vue -->
<template>
  <div class="layout">
    <header><slot name="header" /></header>
    <main><slot /></main>
    <aside><slot name="sidebar" /></aside>
    <footer><slot name="footer" /></footer>
  </div>
</template>

<!-- 親 -->
<Layout>
  <template #header><h1>Site</h1></template>
  <template #sidebar><nav>Menu</nav></template>
  <p>Main content</p>
  <template #footer><p>© 2026</p></template>
</Layout>

合格基準

  • 4 slot が指定位置に描画
  • #name shorthand 使用

Lv3: 発展 — Scoped Slot

問題

<!-- ItemList.vue -->
<script setup lang="ts">
const props = defineProps<{ items: { id: number; name: string }[] }>();
</script>

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

<!-- 親 -->
<ItemList :items="users">
  <template #default="{ item, index }">
    {{ index + 1 }}. {{ item.name }} (#{{ item.id }})
  </template>
</ItemList>

子が構造、親が見た目を決めるパターン。

合格基準

  • scoped slot で子の値を親が使う
  • ループの index + item が表示

★発展: 構造の再利用とスタイルの分離

題材『ニュース記事カード一覧』。

named slot で 構造 は再利用できる。だが スタイル は slot では巻き取れない —

別の仕組み(CSS変数 + scoped class)が要る。この分離原則を体で覚える。

◆Step A: named slot でカードを組む(既出の復習)

枠だけ担う BaseCard(無名 slot)と、領域を切った ArticleCard(named slot)の2層にする。

<!-- BaseCard.vue — 枠(角丸の箱)だけ提供。中身は知らない -->
<template>
  <div class="base-card">
    <slot />
  </div>
</template>
<!-- ArticleCard.vue — thumbnail / title / meta の3領域を切る -->
<template>
  <BaseCard>
    <div class="thumb"><slot name="thumbnail" /></div>
    <h3 class="title"><slot name="title" /></h3>
    <div class="meta"><slot name="meta" /></div>
  </BaseCard>
</template>
<!-- 使う側 — 記事ごとに #title などで注入 -->
<ArticleCard v-for="a in articles" :key="a.id">
  <template #thumbnail><img :src="a.image" :alt="a.title" /></template>
  <template #title>{{ a.title }}</template>
  <template #meta>{{ a.author }} ・ {{ a.date }}</template>
</ArticleCard>

合格基準

  • BaseCard は中身を一切知らず、<slot /> で受け取るだけ
  • ArticleCard の3つの named slot がそれぞれ指定位置に描画される
  • 呼び出し側が <template #title> 等で記事データを注入できる

◆Step B: 装飾をインライン style にベタ書きする(痛みを作る)

まず わざと 角丸とアクセント色を全カードのインライン style に直書きする。

一覧には 5〜6 枚のカードを並べること。

<!-- ArticleCard.vue — 全部インラインに直書き(このあと回収する) -->
<template>
  <div
    :style="{
      borderRadius: '8px',
      borderLeft: '4px solid #3b82f6',
    }"
  >
    <div class="thumb"><slot name="thumbnail" /></div>
    <h3
      class="title"
      :style="{ color: '#3b82f6' }"
    ><slot name="title" /></h3>
    <div class="meta"><slot name="meta" /></div>
  </div>
</template>

ここで体感すること

  • アクセント色を #3b82f6 → 別の色に変えたい
  • 角丸を 8px12px にしたい
  • → インラインだと 全カード・全箇所を手で書き換える ことになる
  • slot で構造は1箇所に集約できたのに、スタイルは集約できていない

合格基準

  • 一覧に 5 枚以上のカードが並んでいる
  • 色・角丸がインライン style に散らばっている状態を再現できている
  • 「色を1つ変えるのに何箇所触るか」を数えて記録する(後で比較する)

◆Step C: CSS変数 + scoped class に巻き取る(負債回収)

散らばった装飾を :root の CSS変数と scoped class に集約する。

インライン style は全廃する。

/* グローバル(main.css など) */
:root {
  --card-radius: 8px;
  --accent-color: #3b82f6;
}
<!-- ArticleCard.vue — 装飾は scoped class へ。変数を参照する -->
<template>
  <div class="article-card">
    <div class="thumb"><slot name="thumbnail" /></div>
    <h3 class="title"><slot name="title" /></h3>
    <div class="meta"><slot name="meta" /></div>
  </div>
</template>

<style scoped>
.article-card {
  border-radius: var(--card-radius);
  border-left: 4px solid var(--accent-color);
}
.title {
  color: var(--accent-color);
}
</style>

合格基準(観測可能)

  • :root--accent-color1行変えるだけ で、一覧の全カードのアクセント色(左罫線+タイトル色)が一斉に変わる
  • --card-radius を 1行変えるだけで全カードの角丸が変わる
  • ArticleCard.vue:style= のインライン直書きが 1つも残っていない
  • 構造(slot)と装飾(CSS変数 + scoped class)が別の仕組みで管理されている

> 学びの核: slot は「何を差し込むか(構造)」の再利用。スタイルは別レイヤ(CSS変数)で再利用する。

> 構造の容器とスタイルのテーマは混ぜず、それぞれの仕組みに分けて持つ。

提出

submission.md に Lv1〜Lv3 の回答と、★発展の Step A〜C の成果(特に Step B→C の前後で「色を変えるのに触る箇所が何箇所→1箇所」になったか)を。

ヒント

  • <slot /> は「親がタグの中に書いた内容」が入る穴(デフォルト slot)。
  • <slot name="header" /> は「親が #header で差し込んだ内容」が入る穴(名前付き slot)。
  • 親側はこう使う:
  <Card>
    <template #header>タイトル</template>
    本文(デフォルト slot に入る)
  </Card>
  
  • props は「値」を渡すが、slot は「テンプレート(HTML の塊)」を渡せるのが違い。

解説

slot は「テンプレートを差し込む穴」

props は (文字列・数値・オブジェクト)を渡す。slot は テンプレート(HTML の塊) を渡せる。

カードの「枠」(角丸・ヘッダの背景・余白)は共通だが、中身は場所ごとに違う。

中身を props で全部受け取るのは無理(見出し・本文・ボタン…構造が毎回違う)。

そこで「枠だけ作って、中身は親に書かせる」のが slot。

<!-- 子: Card.vue -->
<div class="card">
  <div class="card-header"><slot name="header" /></div>  <!-- 名前付き -->
  <div class="card-body"><slot /></div>                   <!-- デフォルト -->
</div>
<!-- 親: 使う側 -->
<Card>
  <template #header>ユーザー情報</template>   <!-- header slot に入る -->
  <p>名前: 山田</p>                            <!-- デフォルト slot に入る -->
</Card>
  • <slot />(デフォルト)… 親がタグの中に直接書いた内容。
  • <slot name="header" />(名前付き)… 親が <template #header> で指定した内容。
  • 名前で振り分くので、ヘッダと本文が混ざらない。

props と slot の使い分け

渡したいもの使うもの
値(タイトル文字列・件数・フラグ)props
構造を持つ中身(任意の HTML・別コンポーネント)slot

Layout(共通の枠)、Modal(中身が毎回違うダイアログ)、Card のような

外枠は共通・中身は呼び出し側が決める」部品は slot で作る。

実アプリの再利用部品は props と slot を組み合わせて設計する。