スロット

最終更新日: 2018年4月26日

このページは コンポーネントの基本 を読まれていることが前提になっています。コンポーネントを扱った事のない場合はこちらのページを先に読んでください。

スロットコンテンツ

Vue は現在の Web Components spec draft にならったコンテンツ配信 API が実装されており、 <slot> 要素をコンテンツ配信の受け渡し口として利用します。

これを使うことで次のようなコンポーネントを作成することが出来ます:

<navigation-link url="/profile">
Your Profile
</navigation-link>

そして、 <navigation-link> のテンプレートはこうなります:

<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>

コンポーネントを描画する時、 <slot> 要素は「Your Profile」に置換されるでしょう。スロットには HTML を含む任意のテンプレートを入れることが出来ます:

<navigation-link url="/profile">
<!-- Font Awesome のアイコンを追加 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>

または、他のコンポーネントを入れることも出来ます:

<navigation-link url="/profile">
<!-- コンポーネントを使ってアイコンを追加 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>

もし <navigation-link><slot> 要素を含んでいない場合、どんなコンテンツが渡されてもただ破棄されるでしょう。

名前付きスロット

複数のスロットがあると便利なときもあります。例えば、仮に base-layout コンポーネントが下記のようなテンプレートだとしましょう:

<div class="container">
<header>
<!-- ここにヘッダコンテンツ -->
</header>
<main>
<!-- ここにメインコンテンツ -->
</main>
<footer>
<!-- ここにフッターコンテンツ -->
</footer>
</div>

こういった場合のために、 <slot> 要素は name という特別な属性を持っていて、追加のスロットを定義できます:

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

名前付きスロットにコンテンツを配信するために、 親の中の template 要素に slot 属性を含めることが出来ます:

<base-layout>
<template slot="header">
<h1>Here might be a page title</h1>
</template>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<template slot="footer">
<p>Here's some contact info</p>
</template>
</base-layout>

もしくは、要素に直接 slot 属性を指定することもできます:

<base-layout>
<h1 slot="header">Here might be a page title</h1>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<p slot="footer">Here's some contact info</p>
</base-layout>

その上で名前無しのスロットを引き続き使うこともでき、デフォルトスロットとして、マッチしなかった全ての要素を受け取る受け渡し口となります。上記の例はどちらも以下のような HTML にレンダリングされるでしょう:

<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>

デフォルトのスロットコンテンツ

デフォルトのコンテンツを持ったスロットがあると便利な場合もあります。例えば、 <submit-button> コンポーネントはデフォルトでは「Submit」という文言にしたいですが、ユーザが「Save」や「Upload」など他の文言に上書き出来るようにもしたいです。

これを実現するためには、slot タグの中にデフォルトコンテンツを記述してください。

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

もし親からコンテンツが提供されていた場合、このデフォルトコンテンツを置き換えます。

コンパイルスコープ

スロットの中でデータを取り扱いたい場合はこうします:

<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>

このスロットはテンプレートの残りの部分と同じインスタンスプロパティ(つまり同じ”スコープ”)にアクセスできます。 <navigation-link> のスコープにアクセスすることはできません。例えば、 url へのアクセスは動作しないでしょう。ルールとして以下を覚えてください:

親テンプレート内の全てのものは親のスコープでコンパイルされ、子テンプレート内の全てものは子のスコープでコンパイルされる

スコープ付きスロット

2.1.0から新規追加

ときどき子コンポーネントからデータにアクセスできる再利用可能なスロットを提供したいでしょう。例えば、簡単な <todo-list> コンポーネントは以下のようなテンプレートで書けます:

<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

しかしアプリケーションのいくつかの部分では、それぞれの todo アイテムがただの todo.text とは違うものをレンダリングしたいでしょう。そういった場合はスコープ付きスロットの出番です。

この機能を実現するために行わなければならないことは、todo アイテムのコンテンツを <slot> 要素で囲い、スロットに対してコンテキストに関連した全てのデータ(この場合は todo オブジェクト)を渡すことです:

<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- それぞれの todo のためのスロットがあり、 `todo` オブジェクトを -->
<!-- スロットのプロパティとして渡している -->
<slot v-bind:todo="todo">
<!-- フォールバックコンテンツ -->
{{ todo.text }}
</slot>
</li>
</ul>

この <todo-list> コンポーネントを利用する時、 slot-scope 属性を使うことで、子からデータにアクセスすることなく、 todo アイテムの代替となる <template> を任意で定義することができます:

<todo-list v-bind:todos="todos">
<!-- スロットスコープの名前として `slotProps` を定義してください。 -->
<template slot-scope="slotProps">
<!-- todo アイテムに対してカスタムテンプレートを定義することで、`slotProps` を -->
<!-- 使ってそれぞれの todo をカスタマイズしてください。 -->
<span v-if="slotProps.todo.isComplete"></span>
{{ slotProps.todo.text }}
</template>
</todo-list>

2.5.0 以降では、slot-scope はもはや <template> に限定されず、どの要素やコンポーネントでも使用できます。

slot-scope の分割代入

slot-scope の値は実際には関数定義の引数の部分にあらわせる有効な JavaScript 式を受け付けます。これはサポートされている環境 (単一ファイルコンポーネント または モダンブラウザ) では ES2015 分割代入 を式の中で下記のように利用できることを意味します:

<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete"></span>
{{ todo.text }}
</template>
</todo-list>

これはスコープ付きスロットを少しきれいにする素晴らしい方法です。