template标签是结构预定义工具,非shadow dom替代品;须用clonenode(true)克隆插入才生效,直接innerhtml写字符串会导致插槽失效、xss风险且难维护。

template 标签不是 Shadow DOM 的替代品,而是结构预定义工具。它不渲染、不执行脚本、不加载资源,只存结构——真正要用,必须配合 cloneNode(true) 手动克隆插入,否则模板内容只会“消失”。
为什么不能直接往 shadowRoot.innerHTML 里写 HTML 字符串
硬拼字符串会让模板逻辑散在 JS 里:难维护、易 XSS、无法复用、样式和结构耦合严重。更关键的是,字符串里写 <slot></slot> 不生效——插槽是运行时机制,只在克隆进 Shadow DOM 后才激活。
- 每次修改都要改 JS 字符串,HTML 结构没法被 IDE 语法高亮或格式化
-
innerHTML = `<div><slot name="header"></slot></div>`中的<slot></slot>不会被识别为插槽,只是普通标签 - 动态属性(如根据
this.getAttribute('size')切换 class)得手动拼接,容易漏转义
template 必须放在 light DOM,且要有明确 id 或可查 selector
浏览器解析时,template 是惰性节点,但 JS 拿不到它,就什么都做不了。它不能写在 shadowRoot 里,也不能靠相对路径找——必须能被 document.querySelector 或 getElementById 稳定获取。
- 推荐放在
底部,或与组件 JS 文件同级的 HTML 中(比如紧挨着<script src="my-card.js"></script>) - 别用 class 或 data-* 当唯一标识——多个实例可能冲突;
id最可靠,如<template id="my-card-template"></template> - 如果模板由构建工具注入(如 Vite 的 HTML 插件),确保它在自定义元素定义前已存在 DOM 中
必须在 connectedCallback 中克隆,且永远用 cloneNode(true)
constructor 里拿不到 template,因为此时文档还没解析完;template.content 是 DocumentFragment,只能被移动一次——直接 appendChild(template.content) 会导致第二次实例化时报错或空白。
-
connectedCallback是安全时机:组件已挂载、DOM 已就绪、template肯定可查 -
template.content.cloneNode(true)是唯一正确方式;false只克隆子节点,会丢掉文本节点和事件绑定上下文 - 克隆后立即操作:设置
textContent、绑定事件、填充slotfallback 内容,不要留到后续异步回调里
<slot></slot> 在 template 里怎么写才有效
<slot></slot> 不是占位符,是内容搬运工。它只在模板被克隆并 append 到带 Shadow DOM 的元素后,才开始分发外部传入的子节点。
- 默认插槽写
<slot></slot>;命名插槽必须加name属性,且值严格匹配(大小写敏感、无空格) - 外部使用时要对应:
<my-card><span slot="header">标题</span></my-card> - 插槽没内容时显示空白?补上 fallback:
<slot>默认提示</slot>,否则用户看不见也不报错 - 别给
<template></template>外层套 div ——template.content只包含其子节点,wrapper 会被丢弃
复杂点在于时机与所有权:模板内容不属于任何 DOM 树,直到你显式克隆并 append;而克隆后的节点归属 shadowRoot,生命周期、事件、样式都受 Shadow DOM 规则约束。漏掉任何一个环节,比如忘了 cloneNode(true) 或把 <slot></slot> 写错位置,整个分发链就断了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











