template标签不渲染且不解析slot,需通过customElements.define配合attachShadow和cloneNode才能激活插槽逻辑;slot仅在自定义元素直系子节点中匹配,且name属性严格区分大小写与空格。

template 标签本身不渲染,也不解析 slot
直接写 <template><slot></slot></template> 不会触发任何插槽逻辑——template 只是内存中一段未激活的 DOM 片段,浏览器不会执行其中的 slot 分发行为。它既不挂载、也不参与布局、更不响应父节点变化。
常见误操作:
- 把
template当成组件直接插入页面:document.body.appendChild(template)→ 内容静默丢弃,控制台无报错 - 在普通 div 里嵌套
<slot name="header"></slot>→ 完全无效,slot只在 Shadow DOM 中起作用 - 期望
template自动桥接内容分发 → 必须靠 JavaScript 显式调用cloneNode(true)并注入shadowRoot
slot 分发只发生在自定义元素的直系子节点上
slot 的匹配逻辑非常苛刻:它只检查自定义元素的**第一层子节点**(即 element.children),且对字符串完全敏感。换行、空格、注释、包裹 div 都会导致匹配失败。
调试关键点:
立即学习“前端免费学习笔记(深入)”;
- 检查
this.shadowRoot.querySelector('slot').assignedNodes()是否为空数组 —— 这是唯一可信依据 - 父级写成
<my-card><h2 slot="header">Title</h2></my-card>✅;但写成<my-card><div><h2 slot="header">Title</h2></div></my-card>❌(非直系) -
slot="header"和<slot name="Header">不匹配 —— 大小写、连字符、前后空格全算差异
动态插槽名必须由父端驱动,子端 slot name 必须静态
原生 Web Components 中,<slot name="xxx"> 的 name 属性不能绑定变量,也不能用表达式。所谓“动态”,只能体现在父组件如何分发内容,而非子组件如何声明插槽。
可行路径:
- Vue 场景下:子组件固定写
<slot name="header"></slot>,父组件用<template v-slot:[dynamicName]></template>动态指定目标 - React 场景下:通过 props 传入 JSX 片段,再手动判断并插入对应位置(无原生 slot 支持)
- 纯 HTML + JS:需监听
slotchange事件,读取assignedNodes()后按规则重新组织 DOM
template + customElements.define 是唯一能落地的组合
真正让 template 和 slot 协同工作的最小闭环,只有这一条路:定义模板 → 创建自定义类 → 在 constructor 中调用 attachShadow → 克隆 template.content 插入 shadowRoot → 注册元素。
示例核心步骤:
- 声明模板:
<template id="card-tpl"><div><slot name="header">Default</slot><slot>Body</slot></div></template> - 注册类:
class MyCard extends HTMLElement { constructor() { super(); const tpl = document.getElementById('card-tpl'); this.attachShadow({ mode: 'open' }).appendChild(tpl.content.cloneNode(true)); } } - 注册元素:
customElements.define('my-card', MyCard) - 使用:
<my-card><h2 slot="header">Real</h2><p>Content</p></my-card>
漏掉任意一步(比如忘记 attachShadow 或没克隆 content),slot 就只是个无意义的标签。最易忽略的是:模板中的样式必须内联在 template 里,否则无法作用于 shadow DOM。



















