slot name属性必须与外部slot="xxx"严格字符串匹配,大小写、空格、连字符均敏感;多个具名slot按顺序分发直系子节点;默认slot仅接收未被具名拦截的第一个匿名子节点;::slotted()仅作用于投射的直系元素本身。

slot name属性必须和外部slot="xxx"严格字符串匹配
大小写、空格、连字符全部敏感,slot="Header"和<slot name="header">完全不匹配。浏览器不做容错,也不报错,内容直接被丢弃。常见错误是命名用驼峰(slot="mainContent")但组件内写短横线(name="main-content"),结果页面空白。
验证方式很简单:在slotchange回调里打印slot.assignedNodes({ flatten: true }),如果为空,先检查这两处字符串是否逐字一致。
卡片布局中多个具名slot要按顺序接收不同子节点
比如卡片需要标题、副标题、操作按钮三个区域,就得在 Shadow DOM 中写三个具名 <slot name="title"></slot>、<slot name="subtitle"></slot>、<slot name="action"></slot>。外部使用时,每个元素必须带对应 slot 属性,且只能是 <my-card> 的直系子节点:
- ✅
<my-card><h1 slot="title">订单详情</h1><p slot="subtitle">2026-08-12</p><button slot="action">编辑</button></my-card> - ❌
<my-card><div><h1 slot="title">...</h1></div></my-card>(<h1>是孙节点,不参与分发)
注意:同名 <slot name="item"> 写两次,不是复制内容,而是按顺序分发——第一个 <slot> 接收第一个 slot="item" 元素,第二个 <slot> 接收第二个,以此类推。
立即学习“前端免费学习笔记(深入)”;
默认slot只收“没被具名slot拦截”的直系子节点
如果卡片模板里除了 <slot name="title"></slot> 还有一个无 name 的 <slot></slot>,那它只会收到那些没写 slot 属性的子节点,而且只认第一个——后面再加几个匿名 <slot> 都是空的。
例如:<my-card><h1 slot="title">标题</h1><p>正文第一段</p><p>正文第二段</p></my-card>,只有第一个 <p> 会进默认 <slot>,第二个 <p> 不会被渲染(仍在 Light DOM 中,但不可见)。
想让多段内容都进来?要么全显式指定 slot 名(如 slot="content"),要么只留一个默认 <slot> 并把所有内容包在一层容器里(但注意:这会让它们变成非直系子节点,又失效了——所以正确做法是让用户自己控制结构,组件只提供 slot 占位)。
::slotted()样式只能作用于投射进来的直系子元素
::slotted(h2) 可以给投进 <slot name="title"> 的 <h2> 加样式,但 ::slotted(.btn span) 或 ::slotted(*):hover 全部无效。浏览器强制限制它只匹配被投射节点本身,不穿透后代。
实际能做的有限:
- ✅
::slotted([slot="action"]) { margin-left: auto; } - ✅
::slotted(.primary) { color: var(--primary-color); }(前提是用户自己写了class="primary") - ❌
::slotted(button .icon)——.icon是button的子元素,不在投射层级上
真要控制深层样式,得靠 CSS 自定义属性透传(如组件定义 :host { --btn-padding: 8px; },用户在投射节点里用 padding: var(--btn-padding)),或者监听 slotchange 后手动遍历 assignedNodes({ flatten: true }) 加 class。
最常被忽略的是:slot 分发不是立即完成的。哪怕你把模板写对了、名字配上了、节点也放对位置了,在 connectedCallback 里调 slot.assignedNodes() 依然大概率为空。必须等 slotchange 事件触发后才能拿到真实投射节点,而且记得传 { flatten: true }——否则 fallback 里的内容、嵌套结构都看不到。



















