<slot> 标签仅在 Web Components 的 Shadow DOM 内生效,不能在普通 HTML 中独立使用;它必须配合自定义元素、attachShadow() 和 customElements.define() 才能实现内容投射。

slot 标签在普通 HTML 中根本不能直接用
浏览器原生不支持 <slot> 作为独立 HTML 元素渲染——它只在 Web Components 的自定义元素内部、配合 Shadow DOM 才生效。如果你直接在 <body> 里写 <slot>默认内容</slot>,页面不会报错,但也不会显示任何插槽行为,它就当普通未定义标签处理,内容照常渲染,但无法被外部内容“注入”或“替换”。
常见错误现象:Uncaught TypeError: Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a node already in the document —— 这往往是因为试图对已挂载的元素反复调用 attachShadow,而不是 slot 本身出错。
- 必须用
customElements.define()注册一个自定义元素(如<my-card>) - 在该元素的
connectedCallback或构造函数中调用this.attachShadow({ mode: 'open' }) -
<slot>只能出现在 shadow root 内部的模板结构里,不能放在 light DOM 中
如何正确声明具名 slot 并接收外部内容
具名 slot 是复用性提升的关键,但名字必须严格匹配,且大小写敏感。比如组件内写 <slot name="header"></slot>,外部就必须用 slot="header" 属性指定投射位置。
使用场景:卡片组件需要灵活替换标题、操作区、底部按钮,又不想为每种组合写多个组件。
立即学习“前端免费学习笔记(深入)”;
示例(组件定义):
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<div class="card">
<header><slot name="header">默认标题</slot></header>
<main><slot>默认正文</slot></main>
<footer><slot name="footer">默认底部</slot></footer>
</div>
`;
}
}
customElements.define('my-card', MyCard);
对应使用方式:
<my-card> <h2 slot="header">用户资料</h2> <p>这里是自定义正文内容</p> <button slot="footer" onclick="save()">保存</button> </my-card>
- 不带
slot属性的内容,默认投射到第一个匿名<slot>(即<slot></slot>) - 多个同名
slot="header"会被合并投射到同一个具名 slot 中(按 DOM 顺序) - 如果外部写了
slot="xxx"但组件内没有对应name="xxx"的<slot>,内容会被丢弃(静默忽略)
slotchange 事件监听不到首次渲染内容
slotchange 事件只在 slot 的可投影内容发生**动态变更**时触发(例如 JS 插入/移除子节点),不包括初始渲染时的静态内容分配。这意味着你不能靠它来“等 slot 填满再初始化 UI”,首次内容加载是同步完成的。
性能影响:频繁触发 slotchange 可能导致布局抖动,尤其在 slot 内含大量节点时;建议用 requestIdleCallback 或节流处理。
- 监听方式:
shadowRoot.querySelector('slot').addEventListener('slotchange', handler) - 想获取当前投射内容?用
assignedNodes({ flatten: true }),注意flatten: true才会展开嵌套 slot - 若需响应式更新(如根据 slot 内容有无切换样式),应结合
MutationObserver监听 light DOM 变化,而非只依赖slotchange
与 Vue/React 的 slot 概念容易混淆
原生 <slot> 是编译时静态分发机制,不支持作用域插槽(scoped slot)、动态 slot 名、或运行时 slot 切换逻辑。Vue 的 <slot name="xxx"> 和 React 的 children + 渲染函数是框架层抽象,和底层 Web Components slot 不是一回事。
兼容性影响:Safari 对 slot 的支持从 v10.1 开始,旧版 iOS 需要 polyfill(如 @webcomponents/webcomponentsjs),但 polyfill 无法完全模拟所有行为,尤其是 assignedNodes() 返回值可能不一致。
- 不要试图在 Vue 组件里直接写
<slot>并期望它被父级 Vue 模板解析——Vue 会把它当普通 HTML 处理 - 如果封装 Web Component 给 Vue 项目用,对外暴露的是自定义元素接口,slot 由使用者按原生规则传入
- React 中需用
dangerouslySetInnerHTML或透传 props 实现类似效果,本质仍是 light DOM 投射,不是 React 的children语义
真正麻烦的地方不在语法,而在于 slot 分发发生在浏览器渲染管线早期,一旦 shadow root 挂载完成,light DOM 子节点就已确定归属。改错一次,后续调试得从 shadow root 结构和 light DOM 节点关系两头查。



















