Slots - 親が描画責務を差し込むコンポーネント拡張ポイント

ロードマップ: Vue.js学習ロードマップ

What(何についてか)

Slots は、子コンポーネントが自分のテンプレート内に「親が描画内容を差し込める拡張ポイント」を定義する仕組みである。

props が「値を子へ渡す契約」であるのに対し、slots は「描画内容を子へ差し込む契約」である。

Vue の slots は段階的に以下へ拡張される。

  • default slot: 1つの差し込み口を持つ
  • named slots: 複数の差し込み口を持つ
  • scoped slots: 子のデータを親へ渡しつつ、親に描画責務を委ねる
  • renderless component: 描画をほぼ持たず、ロジックだけを提供する

Why(なぜ必要か)

コンポーネント設計では、子が持つべき責務と親へ開放すべき責務を分離する必要がある。

  • props だけでは、子の見た目まで固定されやすい
  • slots があると、共通の外枠や振る舞いを子に閉じ込めつつ、中身の描画だけ親に委ねられる
  • named slots により、header, content, footer のような複数領域を持つレイアウト部品を自然に設計できる
  • scoped slots により、子が保持するデータと親が保持する描画責務を接続できる

特に scoped slots は、一覧取得・ページネーション・状態管理などのロジックを子に閉じ込めつつ、各 item の見た目だけを親ごとに差し替えたい場合に有効である。

How(どう動くか)

1. default slot

子は <slot></slot> を置き、親がその中身を渡す。

<!-- parent -->
<FancyButton>
  Click me!
</FancyButton>
<!-- child -->
<button class="fancy-btn">
  <slot></slot>
</button>

この構造では、子が外枠の button とスタイルを担当し、親が内部コンテンツを担当する。

2. Render Scope

slot content は親テンプレートで定義されるため、評価スコープも親に属する。

  • 親テンプレート内の式は親の state を参照する
  • 子テンプレート内の式は子の state を参照する
  • slot の描画位置が子の中にあっても、slot content は子の state を直接参照できない

この制約が、後述する scoped slots の必要性につながる。

3. fallback content

親が slot content を渡さない場合に備え、子側で既定表示を持てる。

<button type="submit">
  <slot>Submit</slot>
</button>

これは props の default 値に近いが、対象は「値」ではなく「描画内容」である。

4. named slots

複数の差し込み口を持つには、子が name 属性付きの <slot> を定義する。

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

親は v-slot または # 省略記法で、どの内容をどこへ差し込むかを指定する。

<BaseLayout>
  <template #header>
    <h1>Page Title</h1>
  </template>
 
  <p>Main content</p>
 
  <template #footer>
    <p>Contact info</p>
  </template>
</BaseLayout>

レイアウトコンポーネントや Card / Modal のような複数領域部品では特に有効である。

5. conditional slots

slot が渡されたときだけ wrapper を描画したい場合は $slots を使う。

<div v-if="$slots.header" class="card-header">
  <slot name="header" />
</div>

これにより、空の header / footer DOM を残さずに済む。

6. scoped slots

通常の slot では親スコープしか見えないため、子が保持するデータを使って親に描画させたい場合は、子が slot props を渡す。

<!-- child -->
<slot :text="greetingMessage" :count="1"></slot>
<!-- parent -->
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

これは「子が持つデータ」を「親が持つ描画責務」に接続する仕組みである。

FancyList パターン

scoped slots の代表例は FancyList である。

  • 子は API 取得, pagination, loading などのロジックを持つ
  • 子は v-for で items を反復する
  • 各 item の表示方法だけを親へ委譲する
<!-- parent -->
<FancyList :api-url="url" :per-page="10">
  <template #item="{ body, username, likes }">
    <div class="item">
      <p>{{ body }}</p>
      <p>by {{ username }} | {{ likes }} likes</p>
    </div>
  </template>
</FancyList>
<!-- child -->
<ul>
  <li v-for="item in items" :key="item.id">
    <slot name="item" v-bind="item"></slot>
  </li>
</ul>

この設計では、ロジックの共通化と描画の差し替えを両立できる。

7. Renderless Components

scoped slots をさらに推し進めると、描画構造をほぼ持たず、ロジックだけを提供する renderless component に到達する。

<MouseTracker v-slot="{ x, y }">
  Mouse is at: {{ x }}, {{ y }}
</MouseTracker>

このパターンは、以下の 3 層構造として理解できる。

flowchart TD
  A["A. ロジック抽象層\n取得・監視・状態管理"] --> B["B. 業務UI部品層\nslot を具体化した部品"]
  B --> C["C. 画面層\n部品を配置して利用する親"]
  • A: 汎用ロジックを持つ層
  • B: A の slot を埋めて意味のある部品にする層
  • C: B を実際の画面で使う層

Vue 3 では、この A 層の多くは renderless component ではなく composable に置き換え可能である。

props と slots の採用判断

props を優先する場合

  • 子が受け取るのが「値」である
  • 子が描画責務を持つ
  • API を厳密に制約したい
  • 表示パターンが固定的である

slots を使う場合

  • 親が描画内容を差し込みたい
  • 子は外枠や配置だけを担当する
  • 複数領域を持つレイアウト部品を作りたい
  • 子のデータを親に渡しつつ描画を委ねたい

実務では、まず props で表現できるなら props を優先し、必要になった時だけ slots を開放する方が API は安定しやすい。

Options API → Composition API 差分(補足)

項目Options API(旧)Composition API(新)
ロジック再利用renderless component や mixins が選択肢になりやすいcomposable が第一候補になる
slot の位置づけ柔軟な描画差し替え手段同じだが、ロジック再利用は composable と役割分担する
複雑な責務分離component ネストが増えがちcomposable + slots でより局所的に分離しやすい

Key Concepts

用語説明
default slot名前なしの基本差し込み口
named slots複数の意味的な差し込み口
fallback content親が渡さない時の既定描画
render scopeslot content は親スコープで評価されるという原則
scoped slots子のデータを親へ渡しつつ描画を委ねる仕組み
renderless component描画を持たず、ロジックだけを提供する component
composableComposition API でロジックを再利用する関数ベースの仕組み