插槽嵌套不会自动发生,必须在父组件Shadow DOM中显式声明slot标签,且子组件内部也需有对应slot接收,否则内容止步于当前Shadow DOM边界。

插槽嵌套不是自动发生的,必须显式声明 slot 标签
很多人以为把一个自定义元素写在另一个的内部,插槽就会自动穿透、层层分发——这是常见误解。Web Components 的 slot 不会跨 Shadow DOM 自动透传,父组件的 slot 内容默认只停留在它自己的 light DOM 中,不会“掉进”子组件的 shadow root 里。
要实现嵌套插槽,必须在父组件的 shadow DOM 中显式使用 <slot>,且该 slot 需要被子组件的结构所“接收”。例如:<my-card><my-button slot="action">提交</my-button></my-card> 能生效,前提是 <my-card> 的 shadow template 里写了 <slot name="action"></slot>,而 <my-button> 内部也得有对应 <slot> 接收内容。
- 没写
<slot>的地方,就是内容“终止点”,后续无法再分发 - 命名 slot 必须名字完全一致(区分大小写),
name="footer"和name="Footer"是两个不同插槽 - 默认插槽(无 name)只能有一个,且不能和具名插槽混用在同一层级的分发逻辑中
<slot> 的 name 和 slot 属性必须严格配对
插槽分发靠的是属性匹配:light DOM 元素上的 slot 属性值,必须等于 shadow DOM 中 <slot name="xxx"> 的 name 值。浏览器按这个键做映射,不走 class 或其他逻辑。
比如你在 <my-dialog> 里写:<h3 slot="title">警告</h3>,那它的 shadow template 就得有 <slot name="title"></slot>;如果错写成 <slot name="header"></slot>,内容就完全不会渲染出来,也不会报错,只是静默丢失。
立即学习“前端免费学习笔记(深入)”;
-
slot属性只能出现在 light DOM 直接子元素上,嵌套一层后(如<div><button slot="action">…</button></div>)就失效了 - 多个元素用同一个
slot名,它们会按 DOM 顺序拼在一起,不是覆盖关系 - 如果某个
<slot>没被任何 light DOM 元素匹配到,它会显示 fallback 内容(即<slot>默认文字</slot>中的文字)
多层嵌套时,::slotted() 作用域只限于当前 shadow root
你可以在父组件样式里写 my-card::slotted(button) { color: red; },但这只会作用于直接塞进 <my-card> 的 button;如果 button 是通过 <my-button> 内部的 slot 插入的,这个样式就管不到它——因为那个 button 实际上属于 <my-button> 的 light DOM,不在 <my-card> 的 slot 分发范围内。
想控制深层嵌套内容的样式,要么让子组件暴露 part(用 ::part(…)),要么让子组件自己定义 ::slotted() 规则,父组件不能越界。
-
::slotted(*)匹配所有投递进来的元素,但无法穿透子组件的 shadow boundary - 不要试图用
my-card ::slotted(my-button ::slotted(button))—— 这种写法无效,CSS 不支持跨 shadow root 的后代选择器 - 如果需要统一控制按钮颜色,更可靠的方式是约定 class(如
class="action-btn")或用 CSS 自定义属性(--btn-color)向下传递
动态添加 slot 内容时,注意 slotchange 事件触发时机
当通过 JS 修改 light DOM(比如用 appendChild 往 <my-form> 里加新字段),浏览器不会立刻重分发 slot;它会在下一个 microtask 后触发 slotchange 事件。这意味着你不能在 appendChild 后马上读取 <slot> 的 assignedNodes(),得等事件。
典型错误写法:el.appendChild(newField); console.log(slot.assignedNodes()); —— 这里大概率输出空数组。
- 监听方式:
slot.addEventListener('slotchange', () => { /* safe to call assignedNodes() */ }); -
assignedNodes({ flatten: true })可以展开所有命名 slot 的节点,但要注意它不包含未匹配的节点 - 如果组件支持异步加载内容(如从 API 拉取表单项),务必在
slotchange回调里更新 UI,而不是依赖初始渲染时机
实际构建复合组件时,最易被忽略的不是语法,而是 slot 的“边界感”:每个 <slot> 都是一道门,内容进来后就归当前组件管,再往里传必须由它主动开另一道门。没这层意识,嵌套很快变成不可维护的黑盒。



















