Web Componentsのslotとは?
slotは👇
👉 「コンポーネントの中身を外から差し込む」ものです。
基本:デフォルトslotの使い方
コンポーネント側
<slot></slot>
使用側
<my-box>
<p>中身</p>
</my-box>
👉 コンポーネント側に定義された <slot> の位置に、使用側の<p>中身</p>がそのまま入る。
差し込まれる位置はどのように決められるか?
👉 「どこに差し込むか」は、コンポーネント側が決めます。
コンポーネント側
<div class="card">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
使用側
<my-card>
<h1 slot="header">タイトル</h1>
<p>本文</p>
<button slot="footer">OK</button>
</my-card>
👇 この場合、slotによる再配置により、こう表示されます。
<div class="card">
<header>
<h1>タイトル</h1>
</header>
<main>
<p>本文</p>
</main>
<footer>
<button>OK</button>
</footer>
</div>
ルール(重要)
👉 つまり、差し込み先には、下記のルールが適用されます。
| 書き方 | 行き先 |
|---|---|
| slot="header" | |
| slot="footer" | |
| 指定なし |
同じslotが指定されている場合はどうなるか?
<my-card>
<h1 slot="header">タイトル1</h1>
<h2 slot="header">タイトル2</h2>
</my-card>
👉 結果
<header>
<h1>タイトル1</h1>
<h2>タイトル2</h2>
</header>
👉 同じslot名は全部入ります。(順番も維持)
slotが無い場合はどうなるか?
<my-card>
<p>本文1</p>
<p>本文2</p>
</my-card>
👉 結果
<main>
<p>本文1</p>
<p>本文2</p>
</main>
👉 全部デフォルトslotに入る。
対応するslotが無いとどうなるか?
<my-card>
<p slot="sidebar">サイドバー</p>
</my-card>
👉 コンポーネントに
<slot name="sidebar"></slot>
が無い場合
👉 表示されません(消えます)
デフォルトslotの注意点
<slot></slot>
👉 デフォルトslotは基本1つしか機能しません。
仮に、2つ定義しても
<slot></slot>
<slot></slot>
👉 最初の1つしか使われない。
フォールバック(デフォルト表示)
コンポーネント側
<div class="card">
<header>
<slot name="header">タイトル未設定</slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
利用側
<my-card>
<p>本文だけ</p>
</my-card>
👉 結果
<div class="card">
<header>タイトル未設定</header>
<main>本文だけ</main>
<footer></footer>
</div>
👉 中身が無いときの保険。使用側で指定されなかった場合のデフォルト値を指定できます。
slotの本質(ここが一番大事)
👉 注意が必要なのは、slotでは、HTMLがコピーされるのではなく「配置が変わる」だけなので、差し込まれたタグの実態は、使用側のDOMに存在したままとなります。
よくある誤解
👉 「slotは中にコピーされる」
❌ 違います
正解
👉 再配置(projection)
つまり:
- 元のDOMは外にある
- 表示だけ中に移動する
その影響(実務ポイント)
✔ イベントは普通に拾える
button.addEventListener("click", ...)
👉 問題なし
✔ shadowRootから直接取れない
this.shadowRoot.querySelector("p") // ❌
✔ 正しい取得方法
const slot = this.shadowRoot.querySelector("slot");
const nodes = slot.assignedNodes();
実務での設計パターン
✔ 良い設計
<app-card>
<span slot="header">タイトル</span>
<p>本文</p>
</app-card>
👉 汎用的で再利用できる
❌ 悪い設計
this.shadowRoot.innerHTML = `
<h1>${title}</h1>
<p>${content}</p>
`;
👉 固定すぎて使い回せない
属性との組み合わせ(最重要)
<user-card name="山田">
<button slot="footer">フォロー</button>
</user-card>
👉 役割分担
| 役割 | 手段 |
|---|---|
| データ | 属性 |
| UI | slot |
👉 これができると設計が一気に綺麗になります。
まとめ
slotのルール
- slot="名前" → 対応するslotへ
- 指定なし → デフォルトslotへ
- 同じ名前 → 複数入る
- slotが無い → 消える
slotの本質
👉 「構造(コンポーネント)と中身(利用側)を分離する仕組み」