原生和必须通过自定义元素+attachshadow()显式桥接才能生效,否则仅作普通标签不参与内容分发;本身不渲染也不解析内部,其内容需克隆至shadow dom才触发分发逻辑。

原生 <template></template> 和 <slot></slot> 无法自动组合生效——必须通过自定义元素 + attachShadow() 显式桥接,否则 <slot></slot> 就是普通标签,不参与任何内容分发。
为什么直接写 <template><slot></slot></template> 没反应
因为 <template></template> 只是声明一段“待克隆内容”,它本身不渲染、不解析内部的 <slot></slot>;而 <slot></slot> 只在 Shadow DOM 中起作用,且只对自定义元素的直系子节点做内容分发。
-
<template></template>内部写<slot name="header"></slot>→ 浏览器当普通 HTML 片段处理,不触发分发逻辑 - 把
<template></template>直接插入页面(如document.body.appendChild(tpl.content))→ 不会渲染,也不会报错,内容静默丢弃 - 想用
<my-card><h2 slot="header">Title</h2></my-card>效果 → 必须先注册合法自定义元素,并在constructor中调用this.attachShadow({ mode: 'open' })
如何用 <template></template> 预定义结构并注入 <slot></slot>
核心是两步分离:用 <template></template> 存结构,用自定义元素挂 Shadow DOM 并桥接两者。不能跳过注册和挂载环节。
- 先定义模板:
<template id="card-tpl"><div class="card"> <header><slot name="header">Default</slot></header><main><slot>Content</slot></main> </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">Title</h2> <p>Body</p></my-card>✅;<my-card><div><h2 slot="header">Title</h2></div></my-card>❌(<h2></h2>是孙节点,被丢弃)
如何检查插槽有没有被正确填充
别只看渲染结果;Shadow DOM 的内容分发是声明式、静态的,但实际投射状态得靠 API 查。
- 在自定义元素内部,调用
this.shadowRoot.querySelector('slot').assignedNodes()—— 返回当前被投射进来的节点列表(含文本节点) - 如果返回空数组,大概率是父子节点关系不对,或
slot属性拼写错误(大小写敏感、不能有空格) -
assignedElements()只返回元素节点,忽略文本节点,适合需要过滤纯文本的场景 - 注意:
slotchange事件才是可靠时机 —— 在connectedCallback里立刻调assignedNodes(),大概率拿到空数组;应监听slot.addEventListener('slotchange', () => { ... }) -
assignedNodes({ flatten: true })才能拿到所有最终投射进来的节点(包括 fallback 内容里的节点)
最容易被忽略的是:slot 分发本质是“投影”而非“移动”,内容始终保留在 Light DOM 中;样式默认不受 Shadow DOM 影响,要用 ::slotted(*) 或 CSS 自定义属性穿透控制;事件冒泡路径走的是 Light DOM,不是 Shadow DOM 内部路径。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











