slot是web components中唯一原生的内容分发机制,仅在shadow dom下生效,需调用attachshadow()、确保内容为直系子元素、严格匹配slot属性与name值,并正确使用::slotted()和slotchange事件。

slot 是 Web Components 中唯一原生支持的内容分发机制,不是语法糖,也不是框架特性——它由浏览器直接实现,且只在 Shadow DOM 环境下生效。没用 attachShadow(),slot 就不会工作;写了 slot 但没配对的 slot 属性或默认接收点,内容就会被丢弃。
为什么 slot 不渲染、内容“消失”了?
这是最常卡住新手的问题:写了 <slot name="header"></slot>,外部也写了 <h2 slot="header">标题</h2>,但页面上啥也没显示。
- 检查是否调用了
this.attachShadow({ mode: 'open' })—— 没挂 Shadow Root,slot根本不参与任何分发逻辑 - 确认外部元素是自定义元素的直系子元素:
<my-card><div><h2 slot="header">标题</h2></div></my-card>中的<h2></h2>不会被识别,因为被<div> 隔了一层 <li> <code>slot元素必须出现在 Shadow DOM 内部模板中,不能写在 Light DOM 里(比如写在<my-card></my-card>标签内部) - 如果用了
{ mode: 'closed' },虽然渲染不受影响,但 JS 无法通过shadowRoot.querySelector('slot')获取节点,调试时会误判为“没生效” -
<h3 slot="title"></h3>只匹配<slot name="title"></slot>,name="Title"或name="title-text"都不匹配 - 没有
slot属性的元素(如<p>正文</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill9263" title="Html2md"><img src="https://img.php.cn/upload/skill/000/000/081/179249945220127.jpg" alt="Html2md" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill9263" title="Html2md" class="overflowclass">Html2md</a> <p class="overflowclass">使用 Readability + Turndown 将 HTML 页面转换为干净的、Agent 友好的 Markdown。去除导航栏、广告、页脚、Cookie 横幅、社交按钮等干扰元素。支持 URL...</p> </div> <a rel="nofollow" href="/xiazai/skill9263" title="Html2md" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>),只会尝试塞进第一个无name属性的<slot></slot>;后面还有几个空<slot></slot>,也不会继续分发 - 多个同名
<slot name="item"></slot>时,Light DOM 中所有slot="item"元素会按顺序依次填入每个 slot(每个 slot 收到一份副本),不是“广播式”复制 - 未被任何
<slot></slot>匹配的节点,保留在 Light DOM 中,但完全不参与渲染(不是隐藏,是跳过) - 正确写法:
::slotted(h2)、::slotted([slot="footer"])、::slotted(.btn) - 错误写法:
::slotted(div h2)、::slotted(h2 > span)、::slotted(*)(通配符无效) -
::slotted()仅在 Shadow DOM 样式表中有效,且只捕获“被投射进来”的节点,不包括 slot 自身或 fallback 内容 - 若需更细粒度控制,得让传入内容自带 class,或在组件内用
assignedNodes()手动遍历处理 - 必须直接绑定在目标
<slot></slot>上:shadowRoot.querySelector('slot[name="header"]').addEventListener('slotchange', handler);绑在父容器或shadowRoot上无效 - 首次渲染完成时不会触发该事件,哪怕 Light DOM 已有内容 —— 它只响应后续的
appendChild、removeChild等 DOM 变更 - 事件不冒泡,也不捕获,无法委托;多个 slot 需分别监听
- 处理函数里建议用
event.target.assignedNodes({ flatten: true })获取当前实际投射的节点列表,而不是读childNodes
具名 slot 和默认 slot 的匹配规则很严格
匹配不是靠“名字相似”,而是精确的字符串比对 + 顺序约束:
::slotted() 样式只能作用于直接子元素
想给投射进来的 <h2></h2> 加样式?别用 h2 { color: red; },那会污染全局;也别指望 .card ::slotted(h2 span) 能生效——::slotted() 不支持后代选择器。
监听 slotchange 事件要注意时机和范围
slotchange 不是“一劳永逸”的响应机制,它只在内容动态变更时触发,而且绑定位置非常关键。
connectedCallback 里立刻拿到 assignedNodes() 的完整结果 —— 得等 slotchange 或用 requestAnimationFrame 延迟读取。










