本身不参与通信,仅预存不渲染的dom结构;真正通信需结合 custom element 的属性监听、事件派发及 shadow dom 动态更新。

HTML <template></template> 本身不参与通信,只负责结构预存
<template></template> 标签的作用是声明一段**不会立即渲染、也不执行脚本**的 DOM 片段。它不是通信机制,而是组件封装时的“模板仓库”。真正触发通信的,是后续用它克隆内容、挂载到 Shadow DOM、再配合属性监听或事件派发的行为。
常见误解是把 <template></template> 当成类似 Vue 的 <slot></slot> 或 React 的 props 接收器——它完全不解析属性、不响应数据变化、不触发任何生命周期。你写 <template id="card"><div name="{{user}}"></div></template>,那个 {{user}} 就是纯文本,不会被替换。
- 必须用 JavaScript 手动取
document.getElementById('card').content.cloneNode(true),再插入到目标位置(比如this.shadowRoot.appendChild(...)) - 如果模板里有
<slot></slot>,那它只在被<my-card><p>Hello</p></my-card>这样调用时才生效,且仅做内容分发,不传递状态 - 模板中不能直接绑定事件或监听属性变更;所有交互逻辑必须在 custom element 类里写
如何让 <template></template> 配合 customElements 实现 props 透传
真正实现“HTML 模板参与通信”的路径只有一条:用 <template></template> 定义结构,再靠 custom element 的 observedAttributes 和 attributeChangedCallback 响应属性变更,最后把解析后的数据注入模板实例。
例如主应用写:<user-card name="Alice" email="alice@example.com"></user-card>,子组件类里要这样处理:
class UserCard extends HTMLElement {
static get observedAttributes() { return ['name', 'email']; }
attributeChangedCallback(name, oldValue, newValue) {
if (this.shadowRoot) {
const tpl = document.getElementById('user-card-tpl').content.cloneNode(true);
// 替换占位符(注意:这里只是简单字符串替换,实际推荐用 innerHTML + textContent 安全处理)
tpl.querySelector('h3').textContent = newValue;
this.shadowRoot.innerHTML = '';
this.shadowRoot.appendChild(tpl);
}
}
connectedCallback() {
if (!this.shadowRoot) this.attachShadow({ mode: 'open' });
}
}
customElements.define('user-card', UserCard);
- 属性值始终是字符串,复杂对象需先
JSON.stringify()再传入,子组件用JSON.parse(this.getAttribute('config')) - 不能靠
this.name = 'Alice'赋值来触发更新,必须调用this.setAttribute('name', 'Alice') - 每次
attributeChangedCallback触发都建议清空shadowRoot并重 render,避免残留节点
为什么不用 <template></template> 直接做父子通信
因为原生 <template></template> 没有上下文绑定能力,它不像 Vue 的 v-bind: 或 React 的 JSX 表达式,无法自动关联父组件 data 或响应式变量。你改了 JS 变量,<template></template> 里的内容不会变;你改了属性,<template></template> 也不会自动 re-render。
典型错误写法:
<template id="list"><ul>
<li v-for="item in items">{{ item }}</li> <!-- 浏览器直接报错:v-for 不识别 -->
</ul></template>
- 所有框架语法(
v-if、{{ }}、:class)在原生<template></template>中无效 - 想动态生成列表?得用 JS 遍历数组,手动创建
document.createElement('li')插入 - 想条件渲染?得在 custom element 类里写
if (this.hasAttribute('loading')) { ... }
容易忽略的坑:模板克隆后事件监听丢失
从 <template></template> 克隆出来的节点,其内部的 onclick 属性或内联事件处理器(如 <button onclick="doSomething()"></button>)**不会自动激活**——因为克隆的是静态结构,没有重新绑定事件。
正确做法是在克隆后,显式为按钮添加事件监听:
const tpl = document.getElementById('card').content.cloneNode(true);
const btn = tpl.querySelector('button');
btn.addEventListener('click', () => {
// 注意:这里不能直接访问外部闭包变量,因为 template 是跨 scope 的
// 更稳妥的方式是 dispatch CustomEvent 并由 custom element 统一处理
this.dispatchEvent(new CustomEvent('action', { detail: { type: 'submit' } }));
});
this.shadowRoot.appendChild(tpl);
- 内联
onclick在克隆后失效,别依赖它 - 克隆节点的事件监听必须在插入 shadowRoot 后、或至少在
connectedCallback里补上 - 如果模板里有多个同类按钮,不要用
querySelectorAll+ 循环绑定,而应委托到 shadowRoot 上用事件代理
真实场景里,<template></template> 只是起点,通信的实质发生在 custom element 的属性监听、事件派发和 Shadow DOM 更新这一整套链路上。漏掉任一环,看起来像“模板没通”,其实是契约没对齐。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











