template 标签不渲染、不执行脚本,必须用 javascript 克隆(clonenode(true))并注入 dom 才能显示内容;其 data-* 属性仅为静态元信息,不参与绑定或渲染;克隆后需重置 id/name 以避免冲突,并同步更新 label[for],确保状态隔离。

template 标签本身不渲染、不执行脚本,不能靠属性自动展开列表——必须用 JavaScript 克隆并注入 DOM,否则页面上什么都不会出现。
为什么 template 里写 data-* 属性没用
很多人误以为给 <template id="item"></template> 加 data-name="user" 就能“绑定数据”,其实这些属性只是静态元信息,浏览器不会解析、不会映射、不会触发任何行为。它和 <div data-id="1"> 一样,只是挂载在节点上的字符串,不参与渲染流程。<ul>
<li>
<code>template.content 是一个 DocumentFragment,不是活的 DOM 节点,data- 属性不会被框架式模板引擎(如 Vue 的 v-for)识别
cloneNode(true)、再手动填充内容template.dataset.xxx 只能拿到模板定义时的固定值,无法反映每次克隆后的实例状态
cloneNode(true) 是复用列表的核心操作
不调用 cloneNode(true),而是直接 appendChild(template.content),会导致模板内容被“移动”而非复制——第一次插入后,template.content 就变空了,后续循环直接失败。
- 必须用
template.content.cloneNode(true):第二个参数true表示深克隆,保留所有子节点和事件监听器(如果有的话) - 克隆后得到的是独立 DOM 片段,可安全修改
textContent、src、value等,不影响原始模板 - 避免用
innerHTML替换内容——会丢失<input>当前输入值、<select></select>选中状态、已绑定的事件
列表项中 id 和 name 必须重置
模板里写 <input id="email" name="email"> 很常见,但克隆 10 次就会产生 10 个相同 id 的元素——这违反 HTML 规范,导致 document.getElementById('email') 只返回第一个,label[for="email"] 全部指向同一个输入框。
- 每次克隆后,遍历所有含
id或name的表单控件,用唯一标识重写:el.id = 'email-' + index、el.name = 'email[' + index + ']' -
label的for属性必须同步更新:label.setAttribute('for', newId) - 不要用
Math.random()生成 ID —— 在快速循环或 SSR 场景下可能重复;推荐用递增计数器或crypto.randomUUID().slice(0, 6)
跨文件复用模板需额外加载逻辑
template 只能在当前 HTML 文档内定义,无法像 include 那样跨文件引用。想让多个页面共用同一套列表模板,得靠外部加载 + 动态注册。
- 把模板存为
list-item.html,用fetch('list-item.html')获取文本,再用DOMParser解析:new DOMParser().parseFromString(html, 'text/html') - 注意 CSP 限制:若启用了
script-src 'self',动态插入的<script></script>不会执行;含交互逻辑的模板建议封装成自定义元素 - IE11 不支持
fetch和DOMParser,需引入 polyfill 或降级为内联template
真正麻烦的不是克隆,而是克隆之后的状态隔离——每个列表项要独立响应、独立校验、独立提交,而这些都依赖你亲手重置 id、name、dataset 和关联的 label。漏掉任意一环,用户点击第 5 个编辑按钮,光标却跳到第 1 个输入框里。











