<template>不是组件系统,仅是不渲染的模具;真正组件需JS驱动初始化、传参、更新、销毁四环节,且须用document.importNode()克隆以保状态、支持slot分发。

<template> 标签本身不是组件系统,它只是个不渲染的模具。真正可组合的组件系统,必须解决初始化、传参、更新、销毁这四个环节——而这些全靠 JS 主动驱动,不是写完 HTML 就自动生效。
为什么直接写 <template id="card"> 不等于有了组件
浏览器会跳过 <template> 内容解析:里面的 <script> 不执行、<img> 不加载、data-* 属性不触发任何逻辑。更关键的是,它没有生命周期、不能响应属性变化、样式也不隔离。
- 常见错误:把
<template>当 Vue 的<template>用,结果页面空白,控制台也无报错 - 常见误操作:用
innerHTML = tmpl.innerHTML插入,导致<input checked>状态丢失、事件监听器被清空 - 插值语法如
{{title}}在纯 HTML 中完全无效,必须手动遍历节点替换
用 document.importNode() 安全克隆模板内容
这是避免表单状态丢失、兼容 Shadow DOM 场景的唯一可靠方式。相比 cloneNode(true),它保留了 <select> 的选中项、<input type="checkbox"> 的勾选态,且能正确处理 <slot> 分发(如果后续升级到 Web Components)。
- 获取模板:
const tmpl = document.getElementById('card-template') - 克隆并深拷贝:
const fragment = document.importNode(tmpl.content, true) - 填充数据时,对文本节点用
textContent,对富文本用innerHTML(注意 XSS 过滤) - 挂载用
container.appendChild(fragment),别覆盖已有结构
让模板支持“传参”本质是工厂函数 + data-bind 属性
纯 <template> 没有属性机制,所谓“传参”就是调用 JS 函数时传入一个配置对象,再由函数映射到 DOM 节点。它适合静态或低频交互场景,不适合高频响应式更新。
使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
立即学习“前端免费学习笔记(深入)”;
- 模板内用语义化
data-bind="title",而不是class="card-title"—— 后者容易和样式类名冲突 - 工厂函数返回
DocumentFragment,不是字符串,避免重复解析 - 若需多次复用同一模板实例,缓存首次生成的 fragment,后续只更新
textContent或dataset值 - 禁止在模板里写死
id="card-1",ID 重复会导致 CSS 选择器失效、JS 查询出错
什么时候该放弃 <template>,直接上 Custom Elements
当你需要以下任一能力时,<template> + 手动 JS 已经撑不住了:
- 属性变更自动触发视图更新(比如
<my-card title="New">改 title,内部标题立刻变) - 样式默认隔离,不污染全局(
Shadow DOM的mode: 'open'是调试友好底线) - 自定义元素名带短横线(
my-card合法,myCard或card直接报错) -
attributeChangedCallback只响应observedAttributes显式声明的属性,JS 直接赋值不会触发
真正容易被忽略的点是:Custom Elements 的 constructor 里不能操作 DOM(此时元素还没挂载),所有初始化逻辑得挪到 connectedCallback;而且 attachShadow 只能调用一次,重复会报错。


















