Vue Slot とは?初心者向けに基本の使い方を徹底解説
はじめに:Vue.jsとコンポーネント指向開発の魅力
Vue.jsは、JavaScriptのフレームワークの中でも特に学習しやすく、柔軟性が高いことから多くの開発者に愛されています。Vue.jsの最大の魅力の一つに「コンポーネント指向開発」があります。
コンポーネントとは、UI(ユーザーインターフェース)を構成する独立した部品のことです。例えば、ウェブサイトであれば「ヘッダー」「フッター」「ナビゲーションメニュー」「ボタン」「記事カード」といった要素をそれぞれ一つのコンポーネントとして作成します。
コンポーネント化することで、以下のようなメリットが得られます。
- 再利用性: 一度作成したコンポーネントは、アプリケーション内の様々な場所で繰り返し利用できます。これにより、コードの重複を防ぎ、開発効率が向上します。
- 管理のしやすさ: 機能ごとに部品が分かれているため、それぞれの部品の修正や管理が容易になります。
- 見通しの良さ: 複雑なUIも小さな部品の組み合わせとして捉えることで、コード全体の構造が分かりやすくなります。
しかし、コンポーネントを再利用可能にするためには、ある問題に直面します。それは、「同じ部品を使いたいけど、中身だけ少し変えたい」というケースです。
例えば、汎用的な「カード」コンポーネントを作ったとします。このカードは枠線や影といった共通のデザインを持っていますが、中に入れる内容は「写真とタイトル」「文章だけ」「ボタン付き」など、様々でありたいはずです。
もし中身の違いごとに別のコンポーネントを作っていたら、再利用性のメリットが半減してしまいます。かといって、全ての可能性をPropsで渡すように設計するのは非現実的です。
ここで登場するのが、Vue.jsの強力な機能である Slot(スロット) です。
Slot(スロット)とは?コンポーネントの「穴」と「差し込み口」
Slotは、コンポーネント内に「コンテンツを差し込むための穴」を提供します。親コンポーネントから子コンポーネントに、単なるデータ(Props)だけでなく、テンプレートの一部やHTML要素そのものを渡して、子コンポーネントの指定された位置に表示させることができます。
例えるなら、Slotは「枠だけ決まっている箱」の「中身を入れるスペース」のようなものです。箱(子コンポーネント)のサイズやデザインは共通ですが、中に入れるもの(親から渡されるコンテンツ)は毎回自由に選べる、といったイメージです。
なぜSlotが必要なのか?具体的なシナリオ
先ほどのカードコンポーネントの例で考えてみましょう。
Card.vue
という汎用的なカードコンポーネントを作りたいとします。このコンポーネントは、カード全体のスタイリング(背景色、角丸、影など)を共通で持ちます。しかし、カードの中身は、場合によって見出しと本文だったり、画像とボタンだったり、リストだったりと様々です。
もしSlotを使わない場合、考えられる方法は以下のようになります。
- 中身の違いごとに別のコンポーネントを作る:
TextCard.vue
,ImageCard.vue
,ListCard.vue
… これはコンポーネントが増えすぎて管理が大変になります。 - 全てのコンテンツをPropsで渡す: テキスト、画像URL、ボタンのラベル、リストデータ… 考えうる全てのPropsを
Card.vue
が持つことになります。これはPropsの数が膨大になり、コンポーネントのインターフェースが複雑になりすぎます。また、渡せるのは基本的にデータに限られ、複雑なHTML構造や他のコンポーネントを渡すのは困難です。
Slotを使えば、Card.vue
はカードの「枠」だけを提供し、中身を配置したい場所に<slot>
タグを置いておきます。親コンポーネントはCard.vue
を使う際に、その<slot>
タグの位置に表示させたいHTMLや他のコンポーネントを記述するだけで済みます。
これにより、Card.vue
はカードという「枠」の再利用性を高め、中身は完全に親コンポーネントに委ねることができます。
Slotのメリットまとめ
- 再利用性の向上: 汎用的なコンポーネント(レイアウトやコンテナなど)を作成しやすくなります。
- 関心の分離: 親コンポーネントは「表示するコンテンツ」に責任を持ち、子コンポーネントは「コンテンツをどのように配置・装飾するか」に責任を持つことができます。
- コードの見通し向上: 子コンポーネントはシンプルな構造を保ちつつ、多様な表現が可能になります。親コンポーネント側で、子コンポーネントの利用時に具体的なコンテンツを記述するため、どのような内容が表示されるのかが親コンポーネントのテンプレートを見れば明確になります。
この記事では、Vue.jsのSlot機能を以下の3つの主要なタイプに分けて、それぞれの使い方と応用について徹底的に解説します。
- 基本のSlot(デフォルトSlot): 一番シンプルで基本的なSlot。
- 名前付きSlot(Named Slots): 複数の異なる場所にコンテンツを差し込みたい場合。
- スコープ付きSlot(Scoped Slots): 子コンポーネントのデータを使って、親コンポーネント側でコンテンツをレンダリングしたい場合。
さあ、Vue.jsの強力なSlot機能をマスターして、より柔軟で再利用性の高いコンポーネント開発を目指しましょう!
基本のSlot(デフォルトSlot)の使い方
最も基本的なSlotは、「デフォルトSlot」と呼ばれるものです。これは、子コンポーネント内の特定の場所に、親から渡されたコンテンツを一つだけ差し込むための機能です。
子コンポーネントでの定義方法
子コンポーネント(例: MyButton.vue
)のテンプレート内の、コンテンツを差し込みたい場所に、単に<slot>
タグを配置します。
“`vue
“`
このMyButton
コンポーネントは、button
タグに共通のスタイルを適用していますが、ボタンの中に表示するテキストやアイコンは決まっていません。<slot>
タグがあることで、親コンポーネントがその中身を指定できるようになります。
親コンポーネントからのコンテンツの渡し方
親コンポーネント(例: App.vue
)から子コンポーネントを使う際、子コンポーネントの開始タグと終了タグの間に記述したコンテンツが、子コンポーネント内の<slot>
タグの位置に差し込まれます。
“`vue
基本のSlotの例
クリックしてください
保存
“`
上記の例では、MyButton
コンポーネントを複数回使用していますが、それぞれ異なるコンテンツ(テキスト、<strong>
タグ、<img>
タグ)を渡しています。これらのコンテンツは、それぞれのMyButton
インスタンス内の<slot>
の位置にレンダリングされます。
最後の例のように、何もコンテンツを渡さなかった場合、<slot>
タグの位置には何も表示されません。
デフォルトコンテンツの設定
親コンポーネントからコンテンツが渡されなかった場合に、代わりに表示しておきたい「デフォルトのコンテンツ」を設定することができます。これは、子コンポーネントの<slot>
タグの開始タグと終了タグの間に記述します。
“`vue
“`
このMyButton
コンポーネントを使用する際に、親から何もコンテンツが渡されなかった場合は、「ボタン」というテキストが表示されます。
“`vue
デフォルトコンテンツの例
送信
“`
デフォルトコンテンツは、そのSlotにコンテンツが提供された場合には完全に置き換えられます。
基本のSlotの制約
基本のSlot(<slot>
にname
属性を指定しないもの)は、子コンポーネントのテンプレート内に複数配置することはできません。もし複数配置した場合、親から渡されたコンテンツは、最初に定義された<slot>
の位置にのみ表示され、それ以降の<slot>
は無視されます。
複数の場所に異なるコンテンツを差し込みたい場合は、「名前付きSlot」を使用する必要があります。
まとめ:デフォルトSlot
- 子コンポーネントのテンプレート内のコンテンツを差し込みたい場所に
<slot>
タグを配置する。 - 親コンポーネントは子コンポーネントの開始タグと終了タグの間にコンテンツを記述して渡す。
<slot>
タグの間にデフォルトコンテンツを指定できる。- 一つの子コンポーネントに配置できるデフォルトSlotは一つだけ。
デフォルトSlotは、コンポーネントの「主要な内容」だけを親に委ねたい場合や、単にボタンやリンクなどのテキスト部分を差し替えたい場合に非常に便利です。しかし、より複雑なレイアウトを持つコンポーネントの場合、複数の領域に異なるコンテンツを差し込みたくなることがあります。そこで登場するのが「名前付きSlot」です。
名前付きSlot(Named Slots)の使い方
デフォルトSlotは一つの場所にしかコンテンツを差し込めませんが、名前付きSlotを使えば、子コンポーネント内の複数の異なる場所に、それぞれ別のコンテンツを差し込むことができるようになります。
なぜ名前付きSlotが必要なのか?
例として、汎用的な「レイアウトコンポーネント」を考えてみましょう。このコンポーネントは、ヘッダー、サイドバー、メインコンテンツ、フッターといった領域を持っているとします。これらの各領域に、親コンポーネントから個別のコンテンツを差し込みたい場合、デフォルトSlotだけでは対応できません。
MyLayout.vue
というコンポーネントが、<slot name="header">
, <slot name="sidebar">
, <slot name="main">
, <slot name="footer">
のように、名前が付いた複数のSlotを持っていることで、親はどのSlotにどのコンテンツを入れるかを指定できるようになります。
子コンポーネントでの定義方法
子コンポーネントのテンプレート内で、コンテンツを差し込みたい場所に、<slot>
タグにname
属性を追加して配置します。
“`vue
“`
この例では、header
, sidebar
, footer
, main
という名前付きSlotと、名前のないデフォルトSlotが一つ配置されています。
ポイント:
* <slot name="...">
のようにname
属性で名前を付けます。
* name
属性がない<slot>
は、デフォルトSlotとして扱われます。名前付きSlotとデフォルトSlotは混在可能です。
* 名前付きSlotの名前はユニークである必要はありませんが、通常は分かりやすさのためにユニークにします。同じ名前のSlotが複数ある場合、親から渡されたコンテンツはそれら全ての同じ名前のSlotに複製されて表示されます(これは意図しない挙動になることが多いので注意が必要です)。
親コンポーネントからのコンテンツの渡し方(v-slot
ディレクティブ)
Vue 2.6以降では、名前付きSlotやスコープ付きSlotにコンテンツを渡す際に、v-slot
ディレクティブを使用することが推奨されています。これは、親コンポーネントのテンプレート内で、子コンポーネントのタグ内に記述します。
v-slot
ディレクティブは、子コンポーネントの<template>
タグに指定するのが一般的です。これは、渡したいコンテンツが複数の要素からなる場合でも、単一のラッパー要素(<template>
)でまとめることができるからです。
構文は v-slot:slotName
となります。ショートハンドとして #slotName
と書くこともできます。
“`vue
名前付きSlotの例
私の素晴らしいウェブサイト
ナビゲーションリンク
メインコンテンツのタイトル
ここにメインコンテンツが入ります。これはデフォルトSlotに渡されます。
名前付きSlot #main に渡された追加のコンテンツです。
© 2023 私の会社
“`
上記の例では、<MyLayout>
タグの中に複数の<template>
タグが記述されています。それぞれの<template>
タグにv-slot:名前
(または#名前
)を指定することで、その<template>
の中のコンテンツが、子コンポーネントの対応する名前付きSlotに差し込まれます。
<template #header>
の内容は<slot name="header">
へ。<template v-slot:sidebar>
の内容は<slot name="sidebar">
へ。<template v-slot>
の内容は<slot>
(デフォルトSlot) へ。<template #main>
の内容は<slot name="main">
へ。<template #footer>
の内容は<slot name="footer">
へ。
ポイント:
v-slot
は、<template>
タグに指定するのが一般的です。これは、複数の要素を一つのSlotにまとめて渡したい場合に便利です。- もし一つのSlotに渡すコンテンツが単一の要素(例:
<h1>
,<p>
など)だけであれば、その要素自体に直接v-slot:名前
を指定することも可能ですが、推奨されるのは<template>
を使用する方法です。特にスコープ付きSlotと組み合わせる場合に、<template>
を使う方が一貫性があり分かりやすいためです。 - デフォルトSlotにコンテンツを渡す場合は、
v-slot
または#
の後ろに何も指定しません (v-slot
または#default
と書くこともできますが、v-slot
と書くのが一般的です)。
旧構文(slot="..."
属性)について
Vue 2.6より前のバージョンでは、名前付きSlotにコンテンツを渡す際に、渡したい要素に直接 slot="..."
という属性を指定していました。
“`vue
名前付きSlotの旧構文 (非推奨)
私の素晴らしいウェブサイト
ナビゲーションリンク
これはデフォルトSlotへ
メインコンテンツの追加情報
© 2023 私の会社
“`
この旧構文は現在非推奨となっており、Vue 3では完全に削除されました。新しいプロジェクトでは必ずv-slot
を使用するようにしてください。既存のVue 2プロジェクトで旧構文を見かけることがあるかもしれませんが、新しいv-slot
構文に移行することが推奨されます。
v-slot
構文は、名前付きSlotだけでなく、スコープ付きSlotも同じディレクティブで扱えるというメリットがあり、より一貫性のある構文となっています。
名前付きSlotとデフォルトSlotの混在時の注意点
子コンポーネントでデフォルトSlot (<slot>
) と名前付きSlot (<slot name="...">
) を両方定義している場合、親コンポーネントからコンテンツを渡す際は、以下のようになります。
v-slot
(または#default
) を指定した<template>
内のコンテンツがデフォルトSlotに入ります。v-slot:名前
(または#名前
) を指定した<template>
内のコンテンツが対応する名前付きSlotに入ります。- いずれの
v-slot
(または#...
) も指定されていない、子コンポーネントタグ直下のトップレベルのコンテンツは、デフォルトSlotに入ります。
“`vue
これはデフォルトSlotに入ります。
これもデフォルトSlotに入ります。
ヘッダーコンテンツ
これは ‘main’ 名前付きSlotに入ります。
フッターコンテンツ
“`
この例では、<MyLayout>
タグの直下にある <p>
と <h2>
要素は、どちらも v-slot
が指定されていないため、まとめてデフォルトSlot (<slot>
) に渡されます。一方で、<template #header>
の内容は header
という名前付きSlotに、<template #main>
は main
Slotに、<template #footer>
は footer
Slotにそれぞれ渡されます。
親コンポーネント内で、デフォルトSlotに渡したいコンテンツが単一のノードだけであれば、<template v-slot>
のラッパーは省略できます。
“`vue
これは省略記法でデフォルトSlotに入ります。
ヘッダーコンテンツ
“`
ただし、複数のノードをデフォルトSlotに渡したい場合は、<template v-slot>
(または <template #default>
) で囲む必要があります。単一ノードの場合でも、明示的に<template v-slot>
と書く方が、デフォルトSlotに渡していることが分かりやすくなる場合もあります。
まとめ:名前付きSlot
- 子コンポーネントで
<slot name="...">
で名前を付けて定義する。 - 親コンポーネントから
v-slot:名前
(または#名前
) を指定した<template>
タグ内にコンテンツを記述して渡す。 - 名前のない
<slot>
はデフォルトSlotとして扱われる。v-slot
(または#default
) はデフォルトSlotにコンテンツを渡す。 - 名前付きSlotとデフォルトSlotは混在可能。
- Vue 2.6以降は
v-slot
構文が推奨(旧構文slot="..."
は非推奨)。
名前付きSlotを使うことで、コンポーネントの内部を複数の明確な領域に分割し、それぞれの領域に異なる種類のコンテンツを外部から注入できるようになります。これは、レイアウトコンポーネントや、ヘッダー・フッター・ボディを持つような複雑なUI部品を構築する際に非常に役立ちます。
さて、ここまでのSlotは、子コンポーネント側は単に「場所」を提供するだけで、差し込まれるコンテンツの内容には関与しませんでした。しかし、中には「親から差し込まれるコンテンツの中で、子コンポーネントが持っているデータを使いたい」というケースがあります。これを実現するのが「スコープ付きSlot」です。
スコープ付きSlot(Scoped Slots)の使い方
これまでのSlotは、子コンポーネントがコンテンツを受け取る「箱」を提供するだけでした。Slot内に記述されたコンテンツは、親コンポーネントのスコープ(データやメソッド)でコンパイル・レンダリングされます。つまり、Slot内のコンテンツからは、子コンポーネント自身のデータには直接アクセスできませんでした。
しかし、子コンポーネントが持っているデータを使って、親コンポーネント側でどのようにコンテンツを表示するかを決めたい、という場面があります。例えば、リストデータを子コンポーネントが持っていて、親はリストの各アイテムを「どのように」表示するかをカスタマイズしたい場合などです。
このような場合に必要になるのが「スコープ付きSlot」です。スコープ付きSlotを使うと、子コンポーネントがSlotを通して親コンポーネントにデータを提供し、親コンポーネントはそのデータを使ってSlot内のコンテンツをレンダリングすることができます。
なぜスコープ付きSlotが必要なのか?具体的なシナリオ
リスト表示コンポーネント(例: ItemList.vue
)を考えてみましょう。このコンポーネントは、APIから取得したアイテムのリストを内部データとして持っていて、それを表示する責任があるとします。
“`vue
- {{ item.name }} – {{ item.price }}円
“`
このコンポーネントはアイテムを表示できますが、表示形式は常に「アイテム名 – 価格円」という固定になります。親コンポーネントから「アイテム名の後ろにカッコ書きで価格を表示したい」「画像があれば一緒に表示したい」「ボタンを付けたい」といったカスタマイズをしたい場合、このままでは対応できません。
Propsで表示形式を制御することも考えられますが、多様な表示形式に対応しようとするとPropsが複雑になりすぎます。
ここでスコープ付きSlotの出番です。ItemList.vue
はリストのデータ (item
オブジェクト) をSlot経由で親に提供し、親コンポーネントは受け取った item
データを使って、リストの各行をどのように表示するかを決定します。
子コンポーネントでの定義方法
子コンポーネント(例: ItemList.vue
)は、Slotを通して親に提供したいデータを、<slot>
タグの属性としてバインドします。これはPropsを子コンポーネントに渡すのと似ていますが、Slotの場合は「親コンポーネントにデータを渡して、Slot内のコンテンツの描画に使ってもらう」という目的になります。
バインドするデータは、任意の属性名で指定できます。例えば、リストの各アイテムデータを渡したい場合は、<slot :item="item">
のように記述します。:item
は親がデータを受け取る際のプロパティ名、右辺の item
は子コンポーネントのローカルデータ(v-for
ループ内の現在の要素など)です。
“`vue
-
{{ item.name }} – {{ item.price }} 円 (デフォルト)
“`
上記の例では、<slot>
タグに :item="item"
と :index="items.indexOf(item)"
という属性をバインドしています。これにより、親コンポーネントは、このSlotに差し込むコンテンツの中で、item
と index
という名前でこれらのデータにアクセスできるようになります。
重要な注意点: スコープ付きSlotのデフォルトコンテンツの中で、子コンポーネントからバインドされたデータ (item
, index
) にアクセスしようとしていますが、これはうまくいきません。デフォルトコンテンツは、そのSlotにコンテンツが提供されなかった場合に「親のスコープで」レンダリングされるからです。子コンポーネントが提供するデータは、親がSlotコンテンツを定義する際に「受け取って使う」ためのものであり、デフォルトコンテンツはその恩恵にあずかれないのです。デフォルトコンテンツで子コンポーネントのデータを使いたい場合は、Propsとして渡すか、Slotを使わない素の表示ロジックを記述する必要があります。上記の例のデフォルトコンテンツは、説明のためにあえて子スコープのデータを使おうとしていますが、実際には常に「デフォルト」というテキストだけが表示されるか、エラーになるはずです(Vueのバージョンやコンパイル設定によりますが、基本的には子スコープのデータにはアクセスできません)。
親コンポーネントでのデータの受け取り方とコンテンツのレンダリング
親コンポーネント(例: App.vue
)は、子コンポーネントを使う際に、v-slot
ディレクティブを使ってスコープ付きSlotからデータを受け取ります。v-slot
ディレクティブの値として、受け取ったデータ全体を格納するための変数名を指定します。
構文は v-slot:slotName="slotProps"
または v-slot="slotProps"
(デフォルトSlotの場合) となります。slotProps
という変数には、子コンポーネントの<slot>
タグでバインドされた全てのデータがオブジェクトとして格納されます。
“`vue
スコープ付きSlotの例
(Index: {{ slotProps.index }})
別の表示形式
({{ index + 1 }}番目)
“`
上記の例では、親コンポーネントがItemList
コンポーネントを使用しています。<ItemList>
タグの開始と終了タグの間に、<template v-slot="slotProps">
という要素を記述しています。
v-slot="slotProps"
は、デフォルトSlot (<slot>
) からデータを受け取りたいことを示しています。slotProps
という変数名で、子コンポーネントの<slot>
からバインドされたデータ({ item: ..., index: ... }
)を受け取ります。<template>
タグの中では、受け取ったslotProps
オブジェクトを使って、リストの各アイテム (slotProps.item.name
,slotProps.item.price
など) を表示するHTML構造を自由に定義しています。
2つ目の例では、v-slot="{ item, index }"
というように、JavaScriptの分割代入 (Destructuring assignment
) の構文を使ってデータを受け取っています。これにより、slotProps.item
や slotProps.index
と書く代わりに、直接 item
や index
という変数名でデータにアクセスできるようになり、コードがより簡潔になります。これは非常によく使われる記法です。
重要なポイント:
- スコープ付きSlotのコンテンツ(
<template v-slot="...">
の中身)は、親コンポーネントのテンプレート内で記述されますが、子コンポーネントのスコープ(子コンポーネントが提供したデータ)にアクセスできる状態でコンパイル・レンダリングされます。 これが通常のSlotとの決定的な違いです。 v-slot
の値(例:slotProps
や{ item, index }
)は、子コンポーネントの<slot>
でバインドされたデータを受け取るための変数名です。- 子コンポーネントが複数のデータをバインドしている場合(例:
:item="item" :index="items.indexOf(item)"
)、親で受け取る変数は、これらのデータをプロパティとして持つオブジェクトになります(例:slotProps = { item: ..., index: ... }
)。
名前付きSlotとスコープ付きSlotの組み合わせ
名前付きSlotとスコープ付きSlotは組み合わせて使用できます。子コンポーネントは、名前付きSlotに対してもデータをバインドできます。
“`vue
“`
親コンポーネントでは、それぞれの名前付きSlotに対して v-slot:名前="slotProps"
の形式でデータを指定して受け取ります。
“`vue
名前付きスコープ付きSlotの例
サイトタイトル: {{ title }}
ようこそ、{{ user.name }} さん! (権限: {{ user.role }})
© {{ year }} All rights reserved.
“`
この例のように、それぞれの<template>
タグで、対応する名前付きSlotやデフォルトSlotから提供されたデータを個別に受け取ることができます。これにより、より柔軟でデータ駆動型のコンポーネントを設計することが可能になります。
旧構文(scope
または slot-scope
属性)について
Vue 2.6より前のバージョンでは、スコープ付きSlotにコンテンツを渡す際に、<template>
タグや要素に scope="..."
または slot-scope="..."
という属性を指定していました。
“`vue
スコープ付きSlotの旧構文 (非推奨)
サイトタイトル: {{ title }}
ようこそ、{{ user.name }} さん!
“`
slot-scope
は scope
の新しい名称としてVue 2.5で導入され、Vue 2.6でv-slot
に置き換えられました。これらの旧構文は現在非推奨であり、Vue 3では完全に削除されています。新しいプロジェクトでは必ずv-slot
を使用してください。
v-slot
構文の利点は、名前付きSlotとスコープ付きSlotの両方を一つのディレクティブで扱える統一感と、<template>
タグ以外にも直接要素に指定できる柔軟性(ただし<template>
が一般的)にあります。
まとめ:スコープ付きSlot
- 子コンポーネントで
<slot>
タグに属性としてデータをバインドする(例:<slot :item="item">
)。 - 親コンポーネントで
v-slot="slotProps"
(デフォルトSlotの場合) またはv-slot:名前="slotProps"
(名前付きSlotの場合) を指定した<template>
タグ内でデータを受け取る。 slotProps
は、子からバインドされたデータをプロパティとして持つオブジェクト。v-slot="{ data1, data2 }"
のように分割代入も可能。- Slot内のコンテンツは、親のテンプレートに記述されるが、子から提供されたデータにアクセスできるスコープでレンダリングされる。
- Vue 2.6以降は
v-slot
構文が推奨(旧構文scope
/slot-scope
は非推奨)。
スコープ付きSlotは、コンポーネントのレンダリング方法を外部からカスタマイズしたい場合に非常に強力です。リストの表示、グリッドの表示、フォーム要素のカスタマイズなど、子コンポーネントがデータを持っていて、そのデータをどのように見せるかを親が細かく制御したいあらゆる場面で活用できます。
Slotの高度な使い方・応用
基本のSlot、名前付きSlot、スコープ付きSlotを理解すれば、ほとんどの場面でSlotを効果的に活用できます。ここでは、さらにSlotの応用的な使い方や、関連する考慮事項について解説します。
Slotと v-if
/ v-for
の組み合わせ
Slot自体をv-if
やv-for
と組み合わせて、Slotを表示するかどうかを制御したり、同じSlotを繰り返し表示したりすることができます。
Slotの表示制御 (v-if
)
子コンポーネントで、特定の条件下でのみSlotを表示したい場合があります。
“`vue
“`
このコンポーネントを使う親側では、showHeader
やshowFooter
というPropsを渡すことで、対応する名前付きSlotが表示されるかどうかを制御できます。
“`vue
ヘッダー
メインコンテンツ
フッター
“`
このように、Slot自体に対してv-if
を適用することで、Slotの存在を動的に制御できます。
Slotの繰り返し表示 (v-for
)
少し特殊なケースですが、子コンポーネントがSlotを複数回繰り返して表示したい場合があります。これは、スコープ付きSlotと組み合わせて使われることが多いです。
先ほどのItemList.vue
は、リストの各アイテムの表示のために、v-for
ループの中で<slot>
を一度だけ使用していました。もし、リスト全体ではなく、子コンポーネントが持っている特定の一連のデータを、親が指定した単一のSlotコンテンツを使って複数回レンダリングさせたい場合に使えます。
“`vue
データを複数回表示します:
“`
このMyRepeater
コンポーネントは、dataArray
というPropsで受け取った配列をループし、ループの各回で<slot :item="item">
をレンダリングします。親コンポーネントは、このSlotを使う際に、受け取ったitem
データを使って表示内容を決めます。
“`vue
Slotとv-forの例
“`
この場合、親コンポーネントの<template v-slot="{ item }">
の中身は一度だけ定義されますが、MyRepeater
コンポーネントの内部でdataArray
の要素数だけ繰り返してレンダリングされます。これは、表示ロジックを親に、データの繰り返し処理を子に任せるという使い分けができます。
動的なSlot名
Slotの名前を動的に切り替えたい場合は、v-slot
ディレクティブの引数を動的に指定できます。これは、角括弧[]
を使って行います。
“`vue
動的なSlot:
“`
この子コンポーネントは、currentSlotName
というPropsで受け取った文字列をSlot名として使用します。親コンポーネントでは、動的なSlot名を指定してコンテンツを渡します。
“`vue
動的なSlot名の例
現在表示されているのは {{ activeSlot }} のコンテンツです。
これはSlot Aに渡されたコンテンツです。
これはSlot Bに渡されたコンテンツです。
“`
この例では、<MyDynamicSlots :current-slot-name="activeSlot">
の中で、親コンポーネントから渡すコンテンツを <template v-slot:[activeSlot]>
のように指定しています。[activeSlot]
の部分はJavaScriptの式として評価され、activeSlot
変数の値(’slotA’ または ‘slotB’)がSlot名として使用されます。これにより、ボタンをクリックするたびに、子コンポーネントが参照するSlotと、そこに差し込まれる親のコンテンツが動的に切り替わります。
注意点: 子コンポーネント側で<slot :name="currentSlotName">
のように動的に名前を指定する場合、親コンポーネントは、その動的な名前に対応するコンテンツを静的な名前付きSlotとして定義しておく必要があります(上記の例の <template #slotA>
, <template #slotB>
の部分)。これは、Vueのテンプレートコンパイルの特性によるものです。つまり、動的な名前を指定してSlotにコンテンツを渡すことはできますが、子コンポーネント自体が完全に未知のSlot名を動的に生成して、それに対応するコンテンツを親に要求する、といった使い方はできません。あくまで、親側が定義済みの複数のSlotコンテンツの中から、子コンポーネントが指定した名前のものを選択して差し込む、というイメージです。
Slot Propsの省略記法 (v-slot="{ item }"
のように)
スコープ付きSlotのセクションで分割代入を使ったデータ受け取りを紹介しましたが、これは非常に便利な省略記法です。
“`vue
({{ index + 1 }}番目)
“`
<template v-slot="{ item, index }">
は <template v-slot="slotProps">
のショートハンドであり、さらに子コンポーネントから渡されるオブジェクト { item: itemデータ, index: indexデータ }
に対して分割代入を行っている記法です。もし子コンポーネントが単一のデータだけをバインドしている場合、例えば <slot :data="myData">
としている場合、親コンポーネントは <template v-slot="{ data }">
のように受け取ることができます。
Slot Propsにデフォルト値を設定する
子コンポーネントから提供されるスコープ付きSlotのデータに対して、親コンポーネント側でデフォルト値を設定することも可能です。これは、子コンポーネントが特定のデータをバインドしなかった場合などに役立ちます。
“`vue
“`
上記のように、分割代入の構文の中で { item, index = 0 }
のようにデフォルト値を指定できます。これにより、もし子コンポーネントが index
をバインドしていなかった場合でも、親コンポーネントのテンプレート内で index
は 0
として扱われるようになります。
Slot内の要素へのアクセス($refs
とSlot)
親コンポーネントからSlotとして差し込んだ要素に、親コンポーネントからアクセスしたい場合があります(例えば、差し込んだフォーム要素にフォーカスを当てたいなど)。
Slot内のコンテンツは親のスコープでコンパイルされるため、親コンポーネントのテンプレート内で、Slotとして差し込む要素にref
属性を付けておけば、親コンポーネントの$refs
からアクセスできます。
“`vue
ログイン
“`
この例では、親コンポーネントの<template v-slot>
内に記述された<input type="email" ref="emailInput">
に対してref="emailInput"
属性が付与されています。このinput
要素は最終的に子コンポーネントであるMyModal
内のデフォルトSlotの位置にレンダリングされますが、$refs
経由でアクセスするのは親コンポーネントからです。親コンポーネントのshowModal
がtrue
になったときに、this.$refs.emailInput.focus()
を呼び出すことで、Slotとして差し込まれたinput
要素にフォーカスを当てることができます。
注意点: Slot内の要素に対するref
は、そのSlotのコンテンツを定義した親コンポーネントからのみアクセスできます。子コンポーネントからは、Slot経由で差し込まれた要素に$refs
で直接アクセスすることはできません。子コンポーネントがSlot内の要素を操作したい場合は、通常はSlot Propsとしてコールバック関数などを親に渡し、親がそのコールバック内で要素を操作する、といった方法を検討する必要があります。
よくある質問とトラブルシューティング
「Slotコンテンツは親のスコープでコンパイルされる」とは?
これはSlotを理解する上で最も重要な概念の一つです。Slotとして子コンポーネントに渡されるHTMLやコンポーネントは、それを記述した親コンポーネントのデータやメソッドのスコープで解釈・コンパイルされます。
“`vue
子コンポーネント
親コンポーネント
{{ parentData }}
“`
上記の例のように、<ChildComponent>
タグの中に記述された <p>{{ parentData }}</p>
や <button @click="parentMethod">
は、親コンポーネントのParentComponent.vue
のdata
やmethods
を参照します。子コンポーネントのchildData
やchildMethod
にはアクセスできません。
この原則があるため、子コンポーネントのデータを使ってSlot内のコンテンツをレンダリングしたい場合に、スコープ付きSlotが必要になるのです。スコープ付きSlotは、この「親のスコープでコンパイルされる」という原則を維持しつつ、子コンポーネントが特定のデータを「親が使えるように提供する」特別な仕組みを提供します。
なぜSlot内で子コンポーネントのデータに直接アクセスできないのか?(スコープ付きSlotの必要性)
前述の「Slotコンテンツは親のスコープでコンパイルされる」という原則のためです。Slot内のコードは、Slotが最終的にレンダリングされる子コンポーネントの実行コンテキストではなく、Slotのコンテンツを定義した親コンポーネントの実行コンテキストで解釈されます。
親が子コンポーネントのインスタンスに対して Slot を定義する際、親は子の内部構造(特に子の data
オプションで定義されたデータ)を知りませんし、知るべきでもありません。子コンポーネントは自身の内部データを外部から隠蔽するべきです。Slotは、親が子コンポーネントの特定の場所に、親自身の定義したコンテンツを差し込むためのメカニズムであり、子コンポーネントの内部データにアクセスするためのものではありません。
スコープ付きSlotは、この基本的な原則を破るものではありません。子コンポーネントは、Slotのバインド属性(例: :item="item"
) を通して、明示的に特定のデータを親に「提供」します。親コンポーネントは、v-slot
ディレクティブを使って、その提供されたデータを「受け取って使う」という形式をとります。これは、子コンポーネントが「このデータはSlotコンテンツのレンダリングに使っていいよ」と許可し、親がそれを受け取る、という協力関係に基づいています。親が子の内部データに勝手にアクセスできるわけではないのです。
Slot内のスタイル
Slotとして差し込まれたコンテンツにスタイルを適用する場合、いくつかの考慮点があります。
- 親コンポーネントのスタイル: Slot内のコンテンツは親のスコープでコンパイルされるため、親コンポーネントのスタイルはSlot内の要素にも適用されます。ただし、親のスタイルが
scoped
属性付きのCSSを使っている場合、通常はSlot内の要素には適用されません(scoped
スタイルの対象は、そのコンポーネント自身のテンプレート内の要素と、そのコンポーネントが作成した他の要素の一部に限られるため)。 - 子コンポーネントのスタイル: Slotは子コンポーネントのテンプレート内に配置されますが、Slot自体はコンテンツの「プレースホルダー」であり、実際に表示されるコンテンツは親から来ます。子コンポーネントのスタイル(特に
scoped
属性付きのCSS)は、子コンポーネント自身のルート要素や、Slot以外の部分の要素には適用されますが、Slotとして差し込まれた親のコンテンツには直接適用されません。
“`vue
この段落にスタイルを適用したい
“`
この例では、親の<style scoped>
にあるp
要素へのスタイルが、Slotとして差し込まれた<p>
要素に適用されます。一方、子の<style scoped>
にある.child-container p
というスタイルは、Slotとして差し込まれた<p>
要素には直接適用されません。(ただし、CSSの継承や、親から渡された要素自体にクラスが付けられている場合は、そのクラスに対する子のスタイルが適用されるなど、複雑なケースもあります)
- Slotコンテンツ自体の内部スタイル: Slotとして差し込まれるコンテンツ自体に、
<style>
タグやインラインスタイルが含まれている場合は、それが適用されます。
結論: Slot内のコンテンツのスタイルは、主にそのコンテンツを定義した親コンポーネントのスタイルシートの影響を受けます。子コンポーネントのscoped
スタイルは、Slotの内容自体には直接影響を与えにくいです。もし子コンポーネント側でSlotコンテンツ内の要素にスタイルを適用したい場合は、Slotコンテンツを特定の要素(例: <div>
)で囲んで、その要素に子コンポーネントのスタイルを適用する、といった工夫が必要になる場合があります。
Slotのパフォーマンスへの影響
Slotの使用自体が直接的なパフォーマンスのボトルネックになることは少ないです。Vueの内部的なSlotの処理は効率的です。
しかし、Slotとして非常に大量の要素や複雑なコンポーネントを差し込んだり、スコープ付きSlotで子コンポーネントが提供するデータ量が膨大だったりする場合、当然ながらレンダリングやデータ処理のコストは増加します。
特に、v-for
ループの中でスコープ付きSlotを使用する場合、ループの各イテレーションごとにSlotコンテンツのレンダリング処理が行われます。この場合、Slotコンテンツが複雑であればあるほど、リスト全体のレンダリングコストは増加します。これはSlotの仕組みそのものよりも、大量のDOM要素やコンポーネントをレンダリングすること自体のコストと言えます。
一般的には、Slotはコンポーネントの再利用性と保守性を高めるための強力なツールであり、適切に使用する限りパフォーマンス上の大きな問題を引き起こすことは稀です。パフォーマンスが気になる場合は、Slotの使い方よりも、レンダリングされる要素数やコンポーネント自体の最適化に注力する方が効果的であることが多いです。
まとめ:Slotの種類と使い分け、そしてその力
この記事では、Vue.jsのSlot機能について、その基本的な概念から、3つの主要なタイプ(デフォルトSlot、名前付きSlot、スコープ付きSlot)、そして応用的な使い方までを詳しく解説しました。
Slotの種類の振り返り:
- 基本のSlot(デフォルトSlot):
<slot></slot>
で定義。親コンポーネントのタグ内に直接記述されたコンテンツが差し込まれます。一つのコンポーネントに一つだけ配置可能。単一のコンテンツを差し込む最もシンプルな方法。 - 名前付きSlot(Named Slots):
<slot name="名前"></slot>
で定義。親コンポーネントは<template v-slot:名前>
または<template #名前>
を使って、特定の名前のSlotにコンテンツを差し込みます。複数の異なる場所にコンテンツを差し込みたい場合に利用。 - スコープ付きSlot(Scoped Slots): 子コンポーネントが
<slot :データ名="データ">
のようにデータをバインド。親コンポーネントは<template v-slot="slotProps">
または<template v-slot:名前="{ データ名 }">
のようにデータを受け取り、そのデータを使ってSlot内のコンテンツをレンダリングします。子コンポーネントが持つデータを使って、親コンポーネント側で表示形式を柔軟に決めたい場合に利用。
それぞれの使い分け:
- コンポーネントの主要な領域に、単一のまとまったコンテンツを差し込みたい → 基本のSlot
- コンポーネント内の複数の明確に区切られた領域(ヘッダー、フッター、サイドバーなど)に、それぞれ異なるコンテンツを差し込みたい → 名前付きSlot
- 子コンポーネントが持っているデータのリストなど、子コンポーネント内のデータを親が定義する形式で表示したい → スコープ付きSlot
Slotを使いこなすことのメリット:
Slotは、コンポーネントをより柔軟で再利用可能なものにするための非常に強力な機能です。
- コンポーネントの汎用化: 見た目や構造は共通で、中身だけが異なるようなコンポーネントを効率的に作成できます(例: カード、モーダル、レイアウト)。
- 責任の分離: コンポーネントは自身の構造やスタイルに責任を持ち、親コンポーネントは Slot を通して差し込む具体的なコンテンツに責任を持つ、という明確な役割分担ができます。
- より表現力豊かなUI: 特にスコープ付きSlotを使えば、子コンポーネントのデータに基づいて、親コンポーネントが細部にわたるレンダリングの決定を下すことができます。これにより、高度にカスタマイズ可能なリストやグリッドなどを実現できます。
- コードの見通し向上: 親コンポーネントのテンプレートを見れば、子コンポーネントのどのSlotにどのようなコンテンツが渡されているのかが一目で分かりやすくなります。
最初はSlotの概念、特にスコープ付きSlotのデータの受け渡し方法(v-slot="{ ... }"
)が少し難しく感じられるかもしれません。しかし、実際に簡単なコンポーネントを作って試してみることで、その仕組みと強力さが実感できるはずです。
是非、ご自身のVue.js開発で積極的にSlotを活用してみてください。きっと、よりクリーンで、保守しやすく、そして柔軟なアプリケーションを構築できるようになるでしょう。
次のステップ
Slotの基本を理解した後は、以下の点について学習を進めると、さらにVue.jsのコンポーネント開発スキルが向上します。
- PropsとEmit: コンポーネント間の基本的なデータやイベントの受け渡し方法を復習し、SlotとProps/Emitをどのように組み合わせて使うかを学ぶ。
- Provide/Inject: コンポーネントツリーの深くにある子孫コンポーネントとデータをやり取りする方法を学ぶ。
- コンポーネント設計パターン: Slot、Props、Emit、Provide/Injectなどを組み合わせて、どのようなコンポーネントを設計できるか、実践的なパターン(Container/Presentationalコンポーネントなど)を学ぶ。
- Render Function / JSX: テンプレート構文ではなく、JavaScriptでコンポーネントをレンダリングする方法を学び、Slotがどのように扱われるか理解する。
SlotはVue.jsのコンポーネント機能の根幹をなす重要な要素です。この解説が、Slotを理解し、自信を持って使いこなすための第一歩となることを願っています。
Happy Coding!