标签用于零运行时开销封装组件结构,需设id、完整自闭合、禁用内联事件和script,克隆后手动绑定事件与状态,并配合命名空间css和显式aria属性保障可访问性。

用 <template></template> 标签封装组件结构,不渲染、不执行脚本
直接把组件 HTML 写在 <template></template> 里,浏览器会跳过解析和渲染,DOM 中完全不出现。这是零运行时开销的前提。
常见错误是误用 <script type="text/template"></script> 或 <div hidden> ——前者需手动解析字符串,后者仍占用 DOM 节点且可能触发样式计算;后者还可能被屏幕阅读器意外读取。<p>正确写法示例:</p>
<pre class="brush:php;toolbar:false;"><template id="c-button"><button class="c-button" type="button"></button>
</template></pre>
<ul>
<li>必须设 <code>id,方便 document.getElementById() 获取
<li> 却不包 <ul></ul>)onclick)、<script></script> 标签或 data- 属性以外的动态属性克隆后绑定数据,避免重复解析模板
每次创建组件实例,都该调用 template.content.cloneNode(true),而不是反复 innerHTML 字符串拼接——后者触发 HTML 解析、DOM 构建、样式计算全流程,性能随实例数线性下降。
容易忽略的是:克隆出来的节点默认无事件监听,也无状态。你需要在插入前显式绑定:
const tmpl = document.getElementById('c-button');
const instance = tmpl.content.cloneNode(true);
const btn = instance.querySelector('button');
btn.textContent = '提交';
btn.addEventListener('click', handler);
document.body.appendChild(instance);
- 不要在
<template></template>内写onclick,那会绑定到文档全局,且无法随实例销毁自动清理 - 若需复用同一份逻辑,把绑定逻辑抽成函数,传入
btn和数据,而非在模板里硬编码 - 克隆后务必检查
instance是否为 DocumentFragment,否则appendChild可能静默失败
配合 CSS 设计令牌与命名空间,防止样式泄漏
仅靠 <template></template> 无法解决样式污染。组件类名必须带统一前缀(如 c-button),CSS 变量必须通过 :root 或组件根元素定义,禁用 px 和 !。
典型翻车现场:
- 写
.button { color: blue; }→ 与项目其他.button冲突 - 用
margin: 8px→ 无法响应主题缩放或高对比度模式 - 漏掉
c-button:focus-visible→ 键盘用户无法感知焦点
推荐结构:
:root {
--c-spacing-xs: 0.25rem;
--c-color-primary: #2563eb;
}
.c-button {
padding: var(--c-spacing-xs) calc(var(--c-spacing-xs) * 2);
color: var(--c-color-primary);
}
不依赖框架时,状态更新必须手动同步 DOM
<template></template> 是静态结构容器,没有响应式机制。组件有状态(比如按钮是否禁用、加载中),就得自己维护并更新对应 DOM 属性。
例如,一个带 loading 状态的按钮:
function createButton(text, isLoading = false) {
const tmpl = document.getElementById('c-button');
const frag = tmpl.content.cloneNode(true);
const btn = frag.querySelector('button');
btn.textContent = text;
if (isLoading) {
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
}
return frag;
}
- 别指望
btn.disabled = true后,下次调用createButton会自动恢复 —— 每次都是全新节点 - 涉及 ARIA 属性(如
aria-busy、aria-expanded)必须显式设置,否则无障碍支持断裂 - 如果状态频繁变更,建议封装成类,用
setState方法集中更新 DOM,而不是散落在各处
真正难的不是写出第一个可复用的 <template></template> 组件,而是让所有组件共享同一套设计约束、状态契约和无障碍规则。一旦松动,后续每个新增组件都会悄悄放大技术债。











