template 标签本身不保存状态,克隆必须用 document.importnode(tmpl.content, true) 才能保留 input checked、textarea 值等原生状态;需配合 attachshadow({ mode: 'closed' }) 实现真正隔离,禁用硬编码 id 改用 data-id,避免内联 script 依赖事件驱动初始化。

template 标签本身不保存状态,克隆后必须用 document.importNode() 才能保留 input checked、textarea 值等原生行为
很多人把子应用 HTML 写进 <template id="subapp"></template>,就以为“模板自带状态记忆”。其实浏览器只是跳过解析——它不执行脚本、不加载资源、也不保留任何 DOM 状态。一旦你用 innerHTML = tmpl.innerHTML 插入,<input checked> 会变未选中,<select></select> 的当前选项丢失,<textarea></textarea> 里打的字全清空。
真正能保留这些的是 document.importNode(tmpl.content, true):它克隆的是 DocumentFragment,不是字符串,所以表单控件的运行时状态(checked、value、disabled)全部保留。这是微前端中复用模板的底线操作。
- 必须从
tmpl.content克隆,不能用tmpl.innerHTML -
cloneNode(true)会丢状态,尤其在 Shadow DOM 场景下不可靠 - 克隆后要手动遍历
fragment.querySelectorAll('[data-bind]')填充数据,别用正则替换{{title}}—— 容易 XSS,且无法处理data-bind="src"这类属性绑定
想隔离样式和 DOM 查询?template 必须配合 attachShadow({ mode: 'closed' })
<template></template> 自身没有任何作用域。哪怕你给它加了 data-app="cart",主应用的 [data-app="cart"] .btn 还是能穿透进来,Ant Design 的 .ant-btn 依然生效。真正的墙只有一道:this.attachShadow({ mode: 'closed' })。
这句必须写在 Custom Element 构造函数里,不能延迟。否则,哪怕你后续往 shadowRoot 里塞内容,样式也早被全局规则污染过了。
-
mode: 'open'允许外部 JS 访问el.shadowRoot,调试方便但等于放弃隔离 -
mode: 'closed'下el.shadowRoot返回null,但 DevTools 仍可查看#shadow-root(需开启“Show user agent shadow DOM”) - Shadow DOM 不自动注入 HTML;
shadowRoot.innerHTML = htmlString会让<style></style>和<script></script>逃逸到全局,必须配合DOMParser+adoptedStyleSheets
id 和 class 在 template 中重复会导致 DOM 查询失效,必须用 data-id 或动态唯一标识
多个子应用共用同一份 <template></template>,填充不同数据后,id="modal" 很容易重复。这时 document.getElementById('modal') 只返回第一个,逻辑直接错乱。HTML 没有模块作用域,id 是全局唯一的硬约束,不是“每个组件内部唯一”。
解决方案很简单:模板里禁用硬编码 id,改用 data-id 或生成唯一值:
- 推荐写法:
data-id="${Date.now()}-${Math.random().toString(36).substr(2, 9)}" - 查询改用
container.querySelector('[data-id="xxx"]'),避免冲突 - class 名也要带业务前缀,比如
class="cart-item__price",而不是class="price"—— BEM 不是可选项,是防撞车的最低成本
模板内 <script></script> 不会自动执行,动态插入后需手动触发或改用事件驱动
把含 <script>console.log('init')</script> 的模板克隆进页面,这段脚本根本不会运行。浏览器只执行初始 HTML 解析阶段遇到的 <script></script>,对动态插入的内容默认忽略。
这不是 bug,是安全机制。强行执行会带来 XSS 风险,所以现代框架都绕开这个坑:
- 不要在模板里写内联
<script></script>,改用自定义事件通知:“我已挂载”,由宿主监听并初始化逻辑 - 如果真需要执行,得手动创建
script元素、设置textContent、再appendChild到 fragment,但要注意 CSP 限制 - 更稳妥的做法是把初始化逻辑抽成函数,在
connectedCallback里调用,而不是依赖模板里的脚本
template 和 Shadow DOM 的组合使用时机——不是“用了就隔离”,而是“不用 attachShadow 就等于没隔离”。很多团队卡在样式穿透上,反复调样式权重、加 !important,却没意识到问题根源不在 CSS,而在 DOM 容器本身没建墙。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











