Vue の Slot って何のためにあるのか?
もう既に Slot を使いこなしている方は即答できる疑問だとは思うのですが、私は中途半端に Vue を使ってきたこともあり、Slot を特に使用していませんでした。
Vue 3 のキャッチアップをするついでに Vue のおさらいを兼ねて公式ドキュメントを見ていたところ、Slot の章に以下の気になる記載がありました。
Vue には Web Components spec draft (opens new window)にヒントを得たコンテンツ配信 API が実装されており、 要素をコンテンツ配信の受け渡し口として利用します。
https://v3.ja.vuejs.org/guide/component-slots.html#%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84
Web Components spec draft のリンク先には、Slots Proposal の Github ページがあるのですが、どうやらここに記載されていることにインスパイアされて Vue の Slot は実装されているようです。私が Vue の Slot をそもそもそんなに使ってこなかったのは、おそらく...Slot の目的がわかっていないため、使い方と利用シーンが結びつなかったのだと考え、ならば、Vue の Slot がインスパイアを受けている大本の Web Components の Slot について学べば、Vue の Slot の目的がわかり、かつ、利用シーンにも結びつくと思い、 Web Components の Slot について調べました。
Slot は Web Components の一部の仕組みだが、そもそも Web Components とは
ブラウザには様々な仕組みを API で提供していますが、Web Components はそのうちの一つであり、ウェブアプリを構築する上で再利用可能なカスタム要素を作成するための仕組みです。
以下は、MDN に記載されている Web Components の説明です。
Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです。
https://developer.mozilla.org/ja/docs/Web/Web_Components
再利用可能なカスタム要素を作成すると、ウェブアプリを構築する上で色々と便利なことがあり、単純に同じコードを何回も書かなくていいようにテンプレートにすることが楽に実現できたり、名前空間をカスタム要素の中だけに絞ることでグローバルでの名前の競合を防げる、といったメリットもあります。
例えば、Web Components には Shadow DOM という仕組みがあり、Shadow DOM は自己完結型のコンポーネントを作成することができます。それにより、名前空間をカスタム要素の中だけに絞ることでグローバルでの名前の競合を防げるというメリットをもたらします。
<video> は Shadow DOM を使って実装されており、このタグには様々な DOM が組み込まれていますが、まるっと <video> として定義されており、<video> の中で定義されている CSS はグローバルに影響は与えません。
Shadow DOM v1: 自己完結型ウェブ コンポーネント
https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=ja
Shadow DOM は、Web Components を説明する良い例ですが、他にもウェブアプリを構築する上で再利用可能なカスタム要素を作成するための仕組みはたくさん提供されており、その中に Slot も含まれています。
Shadow DOM と template で作るテンプレート
Slot に触れる前にテンプレートについて詳しく見ていきます。<template> と 先ほど触れた Shadow DOM を組み合わせることで、とても楽でナイスなテンプレートが作ることができます。
<template> とは JavaScript を使用する HTML を保持するメカニズムです。JavaScript で使用する用なのでページには描画されません。
<template id="my-paragraph">
<p>My paragraph</p>
</template>
上記のように<template>で定義した内容を以下のように Javascript で利用できます。
let template = document.getElementById('my-paragraph');
let templateContent = template.content;
document.body.appendChild(templateContent);
上記の例はシンプルな<template>の使い方でしたが、これをカスタムコンポーネントとして定義することで、楽でナイスなテンプレートが作成できます。
以下のように customElements によってカスタムコンポーネントを定義します。カスタムコンポーネントの名称は、 my-paragraph として、<template> の内容をカスタムコンポーネントに登録します。このとき、<video> と同じように Shadow DOM (attachShadow) を使って実装します。
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById('my-paragraph');
let templateContent = template.content;
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(templateContent.cloneNode(true));
}
})
カスタムコンポーネントとして登録すれば、<body> 内で好きに使うことができます。
<body>
<p>hello world</p>
<my-paragraph></my-paragraph>
<my-paragraph></my-paragraph>
<my-paragraph></my-paragraph>
<my-paragraph></my-paragraph>
</body>
このように、<template> とcustomElementsをうまく使ってカスタムコンポーネントを使用することで、Web ページ上で同じ構造を繰り返し使用する必要がある場合にテンプレートとしてまとめることができます。単純に同じコードを何回も書かなくていいようにテンプレートにすることができます。そして、その実装には Web Components である Shadow DOM を使っています。
Web Components の Slot とは?
先程、作成した<my-paragraph>というカスタムコンポーネントは、<p>My paragraph</p> という文章が差し込まれていますが、これを <p>Your paragraph</p> にしたいときもあります。カスタムコンポーネントとして定義した後にコンテンツを差し込めるようにしたほうが便利であり、それを可能にしているのが Web Components の Slot です。
<body>
<p>hello world</p>
<!-- <p>My paragraph</p> が表示される -->
<my-paragraph></my-paragraph>
<!-- <p>Your paragraph</p> が表示される -->
<my-paragraph>
<span slot="my-text">Your paragraph</span>
</my-paragraph>
</body>
Slot を説明するために長々と Web Components や Shadow DOM や テンプレートについて記載しましたが、Slot 自体はシンプルな Web Components で説明は以上です。
Vue の Slot を見てみる
Vue の Slot は、Web Components の Slot のインスパイアを受けていることもあり、Web Components の Slot と想定の利用シーンは同じです。<slot> 要素をコンテンツ配信の受け渡し口として利用しており、例えば、以下のような子コンポーネントを定義します。
<!-- 子コンポーネント(<navigation-link>) -->
<template>
<a v-bind:href="url" class="nav-link">
<slot></slot>
</a>
</template>
親コンポーネントで子コンポーネントを呼び出し、Your Profile という文字列を Slot に受け渡します。
<!-- 親コンポーネント -->
<!-- <p>Your paragraph</p> が表示される -->
<navigation-link url="/profile">
Your paragraph
</navigation-link>
まとめ
Web Components も Vue もテンプレートに対して、コンテンツを後から差し込むための仕組みとして Slot は使われているということがわかりました、以上終わり。
参考
Slots Proposal
https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md
0001-new-slot-syntax.md
https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md
Shadow DOM v1: 自己完結型ウェブ コンポーネント
https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=ja
template と slot の使い方
https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_templates_and_slots
web-components-examples
https://github.com/mdn/web-components-examples/tree/master/simple-template
Web Components
https://developer.mozilla.org/ja/docs/Web/Web_Components
Window.customElements
https://developer.mozilla.org/ja/docs/Web/API/Window/customElements
コンテンツテンプレート要素
https://developer.mozilla.org/ja/docs/Web/HTML/Element/template