1. 什么是 <slot> 元素
<slot> 是 HTML 中一个非常特殊的内置元素,它是 Web Components 标准(Custom Elements + Shadow DOM)的核心组成部分之一。它的作用是在自定义元素的 Shadow DOM 内部定义一个「插槽」,用来接收并展示外部传入的内容。
简单来说,<slot> 就像是一个占位符。当你在页面中使用一个自定义元素时,写在元素标签内部的内容并不会直接显示,而是会被「分发」到 Shadow DOM 中对应的 <slot> 位置进行渲染。
html
<!-- 自定义元素的使用方式 -->
<my-card>
<h2>卡片标题</h2>
<p>这是卡片的内容。</p>
</my-card>
在上面的例子中,<h2> 和 <p> 就是 Light DOM(普通 DOM)中的内容。它们会被分发到自定义元素内部定义的 <slot> 中。
2. 为什么需要 <slot>
在没有 <slot> 之前,自定义元素的内容是「封闭」的。Shadow DOM 提供了样式和结构的隔离,但代价是外部无法向元素内部传递结构化的内容。<slot> 的出现打破了这种封闭性,实现了「结构复用」与「内容定制」的平衡。
它的核心价值体现在:
- 内容分发:允许使用者向组件内部注入任意 HTML 结构。
- 组合复用:组件可以定义默认结构和样式,同时允许外部覆盖或补充内容。
- 语义清晰:通过具名插槽,可以让同一组件在不同场景下呈现不同的布局。
3. <slot> 的基本用法
3.1 默认插槽
不设置 name 属性的 <slot> 就是默认插槽。所有未指定 slot 属性的子元素,都会被分发到默认插槽中。
html
<template id="my-card-template">
<style>
.card {
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
}
</style>
<div class="card">
<slot></slot>
</div>
</template>
<script>
class MyCard extends HTMLElement {
constructor() {
super();
const template = document.getElementById('my-card-template');
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('my-card', MyCard);
</script>
使用方式:
html
<my-card>
<h2>你好,世界</h2>
<p>这段内容会显示在卡片内部。</p>
</my-card>
3.2 具名插槽
通过 name 属性可以定义具名插槽。外部内容需要设置对应的 slot 属性才能匹配到指定的插槽。
html
<template id="my-layout-template">
<style>
header { background: #f0f0f0; padding: 8px; }
main { padding: 8px; }
footer { background: #e0e0e0; padding: 8px; }
</style>
<header><slot name="header">默认头部</slot></header>
<main><slot>默认内容</slot></main>
<footer><slot name="footer">默认底部</slot></footer>
</template>
<script>
class MyLayout extends HTMLElement {
constructor() {
super();
const template = document.getElementById('my-layout-template');
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('my-layout', MyLayout);
</script>
使用方式:
html
<my-layout>
<span slot="header">自定义头部</span>
<p>这是主体内容,会进入默认插槽。</p>
<span slot="footer">自定义底部</span>
</my-layout>
4. 插槽的匹配规则
插槽的匹配遵循以下规则:
- 子元素通过
slot="名称"属性指定要进入的具名插槽。 - 没有设置
slot属性的子元素,自动匹配默认插槽(即没有name属性的<slot>)。 - 如果某个具名插槽没有对应的内容传入,则显示
<slot>标签内部的「回退内容」(fallback content)。 - 如果传入的内容没有对应的插槽可以匹配,则该内容不会被渲染。
html
<template id="my-tip-template">
<style>
.tip { border-left: 4px solid #42b983; padding: 8px 12px; background: #f6ffed; }
</style>
<div class="tip">
<slot name="title">提示</slot>
<slot>默认提示内容</slot>
</div>
</template>
当外部没有传入 title 插槽内容时,会显示回退内容「提示」。
5. 动态插槽与更新
插槽的内容是「响应式」的。当 Light DOM 中的内容发生变化时,Shadow DOM 中的插槽会自动更新,无需手动操作。
html
<my-card id="card">
<h2 id="title">初始标题</h2>
</my-card>
<script>
// 动态修改插槽内容
document.getElementById('title').textContent = '更新后的标题';
</script>
此外,还可以通过 slotchange 事件监听插槽内容的变化:
html
<script>
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = '<slot></slot>';
const slot = shadow.querySelector('slot');
slot.addEventListener('slotchange', (e) => {
console.log('插槽内容发生了变化');
});
}
}
customElements.define('my-card', MyCard);
</script>
6. 多层级插槽与嵌套
<slot> 支持嵌套使用。当一个自定义元素内部又使用了另一个自定义元素时,插槽可以「穿透」传递内容。
html
<template id="outer-template">
<div class="outer">
<my-inner>
<slot></slot>
</my-inner>
</div>
</template>
这种嵌套机制让组件之间的内容传递更加灵活,但也需要注意:插槽的匹配是基于「直接子元素」的,嵌套层级过深时可能需要显式指定 slot 属性。
7. 浏览器兼容性与注意事项
7.1 兼容性
<slot> 元素在现代浏览器中已经得到广泛支持,包括 Chrome、Firefox、Safari 和 Edge。对于旧版浏览器,可以使用 @webcomponents/webcomponentsjs 之类的 polyfill 进行兼容。
7.2 注意事项
<slot>只能出现在 Shadow DOM 内部,在普通 DOM 中使用没有意义。- 插槽分发的是「节点」,而不是「字符串」,因此可以传递复杂的 DOM 结构。
- 样式隔离:Shadow DOM 内部的样式默认不会影响插槽内容,但插槽内容继承外部样式。
- 使用
::slotted()伪元素可以为插槽内容设置样式。
css
::slotted(h2) {
color: #42b983;
}
8. 总结
<slot> 元素是 Web Components 中实现内容组合与复用的关键机制。通过默认插槽和具名插槽,开发者可以构建出结构清晰、高度可定制的组件体系。理解插槽的匹配规则、回退内容以及动态更新机制,是掌握现代前端组件化开发的重要一步。