JavaScript 中的 Web 组件插槽(Slots)与内容分发机制
字数 1032 2025-12-13 11:47:05
JavaScript 中的 Web 组件插槽(Slots)与内容分发机制
描述
Web 组件插槽(<slot>)是 Shadow DOM 中的占位符,允许用户在自定义元素中插入自定义内容。它实现了内容分发机制,类似于传统框架中的"插槽"概念,但属于原生浏览器标准。插槽使得自定义元素既能封装内部结构,又能灵活接收外部内容,是创建可复用组件的关键技术。
详细讲解
1. 插槽的基本概念
- 插槽:
<slot>元素是 Shadow DOM 内的一个"洞",外部传入的内容会在这个位置显示。 - 默认内容:插槽内可以包含备用内容,当外部没有提供内容时显示。
- 具名插槽:通过
name属性为插槽命名,实现精确的内容分发。
2. 基本插槽实现步骤
让我们创建一个带有插槽的自定义元素:
// 1. 定义自定义元素类
class MyCard extends HTMLElement {
constructor() {
super();
// 2. 创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
// 3. 定义模板,包含插槽
const template = document.createElement('template');
template.innerHTML = `
<style>
.card {
border: 1px solid #ccc;
padding: 20px;
border-radius: 8px;
}
::slotted(h2) {
color: blue; /* 仅作用于分发到插槽的 h2 元素 */
}
</style>
<div class="card">
<slot>默认文本(当外部无内容时显示)</slot>
</div>
`;
// 4. 克隆并附加模板
shadow.appendChild(template.content.cloneNode(true));
}
}
// 5. 注册自定义元素
customElements.define('my-card', MyCard);
使用组件:
<!-- 外部提供内容,替换默认插槽 -->
<my-card>
<h2>自定义标题</h2>
<p>这是自定义内容</p>
</my-card>
<!-- 不提供内容,显示默认内容 -->
<my-card></my-card>
3. 具名插槽(Named Slots)
当需要多个插槽时,使用具名插槽进行精确分发:
class UserProfile extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const template = document.createElement('template');
template.innerHTML = `
<div class="profile">
<div class="header">
<slot name="header">默认头部</slot>
</div>
<div class="content">
<slot name="content">默认内容</slot>
</div>
<div class="footer">
<slot name="footer"></slot> <!-- 无默认内容 -->
</div>
<!-- 未指定 name 的插槽捕获所有未分配的内容 -->
<slot>其他内容</slot>
</div>
`;
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('user-profile', UserProfile);
使用具名插槽:
<user-profile>
<h2 slot="header">用户信息</h2>
<p slot="content">姓名:张三</p>
<button slot="footer">保存</button>
<!-- 这个 p 元素没有 slot 属性,会进入未命名插槽 -->
<p>额外信息</p>
</user-profile>
4. 插槽的工作原理
- 分发过程:当自定义元素被渲染时,浏览器执行"插槽分配":
- 遍历自定义元素的子节点
- 如果子节点有
slot属性,就寻找 Shadow DOM 中匹配的具名插槽 - 匹配的子节点从原位置"移动"到对应插槽位置(视觉上)
- 无
slot属性的子节点进入默认插槽
- 实际 DOM 结构不变:插槽内容在 DOM 树中仍位于自定义元素下,只是视觉上被渲染到插槽位置。
5. 插槽事件与样式
- 事件冒泡:插槽内元素的事件会冒泡到自定义元素外部
- CSS 样式:通过
::slotted()伪元素选择器可以样式化插槽内容/* 仅样式化直接插槽的内容 */ ::slotted(p) { font-size: 16px; } /* 只影响顶级插槽元素,不作用于子元素 */ ::slotted(.warning) { color: red; }
6. 编程式操作插槽
可以通过 JavaScript API 操作插槽:
class DynamicSlot extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<div>
<slot name="dynamic"></slot>
</div>
`;
}
// 获取插槽信息
getSlotInfo() {
// 获取所有插槽
const slots = this.shadowRoot.querySelectorAll('slot');
// 查找特定名称的插槽
const dynamicSlot = this.shadowRoot.querySelector('slot[name="dynamic"]');
// 获取分配到插槽的节点
if (dynamicSlot) {
const assignedNodes = dynamicSlot.assignedNodes();
const assignedElements = dynamicSlot.assignedElements();
console.log('分配到插槽的节点:', assignedNodes);
console.log('分配到插槽的元素:', assignedElements);
}
// 检查插槽是否有内容
const defaultSlot = this.shadowRoot.querySelector('slot:not([name])');
if (defaultSlot) {
console.log('默认插槽是否有内容:', defaultSlot.assignedNodes().length > 0);
}
}
}
customElements.define('dynamic-slot', DynamicSlot);
7. 插槽变化监听
监听插槽内容的变化:
class ObservableSlot extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<slot name="observable"></slot>
`;
// 获取插槽元素
const slot = shadow.querySelector('slot');
// 监听插槽内容变化
slot.addEventListener('slotchange', (event) => {
const targetSlot = event.target;
const assignedNodes = targetSlot.assignedNodes();
console.log('插槽内容变化:', {
插槽名称: targetSlot.name || 'default',
节点数量: assignedNodes.length,
节点列表: assignedNodes
});
});
}
}
customElements.define('observable-slot', ObservableSlot);
8. 高级模式:后备内容与插槽链
- 后备内容链:插槽内可以嵌套插槽
- 条件显示:结合 CSS 实现复杂逻辑
class AdvancedSlot extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.container { display: contents; }
.fallback { display: none; }
slot:empty + .fallback {
display: block;
color: gray;
}
</style>
<div class="container">
<slot>
<!-- 主插槽 -->
<slot name="fallback-content">
<!-- 后备插槽 -->
<span class="fallback">无内容</span>
</slot>
</slot>
</div>
`;
}
}
customElements.define('advanced-slot', AdvancedSlot);
关键点总结
- 插槽是内容占位符,不是 DOM 移动器
- 具名插槽实现精确的内容分发
::slotted()伪类用于样式化插槽内容slotchange事件监听插槽内容变化- 插槽内容保持原有事件行为,支持事件冒泡
- 后备内容机制提高组件健壮性
- 编程式访问通过
assignedNodes()和assignedElements()
插槽机制是 Web 组件的核心特性之一,它平衡了组件的封装性和灵活性,使得自定义元素既能保持内部实现的黑盒性,又能提供丰富的内容定制能力。