<template> 是独立于 Web Components 的原生惰性 HTML 容器,不依赖 Custom Elements 或 Shadow DOM,仅通过 cloneNode 或 importNode 激活;它无生命周期、无作用域隔离,常与 Shadow DOM 结合实现样式和行为封装。

template 标签不是 Web Components 的子集,而是独立基础能力
很多人误以为 <template> 是 Web Components 的一部分,其实它早在 Custom Elements 和 Shadow DOM 出现前就已存在,且不依赖任何组件 API。它只是浏览器原生的“惰性 HTML 容器”,内容不会被解析、执行或渲染,直到你显式调用 document.importNode() 或 content.cloneNode(true) 插入 DOM。
这意味着:你可以单独用 <template> 做静态复用(比如表格行模板),也可以把它作为 Web Components 的内部结构载体——但二者没有绑定关系。
-
<template>本身不触发任何生命周期,也不提供封装或作用域隔离 - 在自定义元素中,
<template>通常放在render()或connectedCallback()里用于生成 Shadow DOM 内容 - 若直接用
innerHTML拼接字符串代替<template>,会丢失事件绑定、脚本执行控制和样式作用域边界
用 template + Shadow DOM 构建可复用卡片组件
典型场景:一个商品卡片需要复用 HTML 结构、样式和交互逻辑,同时避免全局样式污染和事件冲突。
关键不是把 HTML 文件“导入”,而是把结构、样式、行为打包进一个自定义元素类里。下面是最简可行结构:
立即学习“前端免费学习笔记(深入)”;
<template id="product-card">
<style>
:host { display: block; border: 1px solid #ddd; padding: 12px; }
.price { color: #e63946; font-weight: bold; }
</style>
<h3 class="title"><slot name="title">默认标题</slot></h3>
<p class="price"><slot name="price">¥0</slot></p>
<button id="buy-btn">立即购买</button>
</template>
<script>
class ProductCard extends HTMLElement {
constructor() {
super();
const template = document.getElementById('product-card');
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
this.shadowRoot.querySelector('#buy-btn').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('buy', {
detail: { title: this.getAttribute('data-title') }
}));
});
}
}
customElements.define('product-card', ProductCard);
</script>
-
<template>必须在定义组件类之前声明,否则document.getElementById()找不到 -
:host是 Shadow DOM 中唯一能影响自定义元素自身样式的伪类,不能用.product-card -
<slot>支持具名插槽,外部使用时写<product-card><span slot="title">iPhone 15</span></product-card> - 事件必须通过
this.dispatchEvent()向外抛出,父级用addEventListener('buy', ...)监听
为什么不用 HTML Imports?它已被废弃且无替代方案
HTML Imports 曾是官方推荐的“HTML 文件导入”机制,但早在 Chrome 73 和 Firefox 65 中就被移除,目前所有主流浏览器都不支持 <link rel="import">。试图复用现有 header.html 或 footer.html 文件时,这条路已彻底堵死。
常见错误现象:Failed to load resource: net::ERR_UNKNOWN_URL_SCHEME 或控制台静默无反应,是因为浏览器根本不再解析该标签。
- 不要尝试 polyfill —— 所有已知 polyfill(如 webcomponentsjs)对 HTML Imports 的支持早已停止维护
- 如果项目里还残留
<link rel="import" href="header.html">,必须全部替换为 JS 动态加载或 Web Components 封装 - 动态加载可用
fetch()+insertAdjacentHTML(),但要注意 XSS 风险和无 Shadow DOM 隔离 - 真正可持续的复用路径只有一条:把每个 HTML 片段重构为
class XXX extends HTMLElement并注册
Stencil 编译器不处理纯 HTML 文件,只认 TSX/TS 组件
如果你正在用 Stencil 构建 Web Components,把一个写好的 card.html 直接扔进 src/components/ 目录下,它不会被编译、不会生成 JS 文件、也不会出现在 dist/ 中——整个过程静默失败。
根本原因在于 Stencil 是 TypeScript 编译器,不是模板引擎。它只扫描带 @Component 装饰器的 .tsx 或 .ts 文件,并将其编译为标准 Custom Element。
- HTML 中的
onclick="doX()"、id="xxx"、class="btn"在 Stencil 中全部无效,必须转为 JSX + 类方法 + 属性驱动 - 样式必须抽离为
.css文件,并在@Component中通过styleUrl引入 - DOM 查询不能用
document.getElementById(),而要用this.el.shadowRoot.querySelector() - 生命周期钩子(如
componentWillLoad)替代了原生connectedCallback,但底层仍是 Custom Elements API
真正卡住人的地方,从来不是语法,而是思维切换:从“写页面片段”转向“定义可实例化、可配置、可通信的元素类型”。一旦习惯这种建模方式,复用才真正落地。



















