<template>本身不解析属性,仅作静态HTML容器;必须通过JS克隆后手动注入dataset值,配合Shadow DOM实现安全、可维护的模板填充。

用 <template> 声明结构,但别指望它自动读取外部属性
<template> 本身只是个“不渲染的占位容器”,它不会解析或绑定任何属性——data-* 、id、class 都只是静态字符串,不会变成运行时变量。常见错误是把模板写成:<template id="card"><div class="card" data-theme="{{theme}}">...</div></template>,然后期待 JS 自动替换 {{theme}}。浏览器根本不认这种语法,它只会原样存着那段字符串。
真正能起作用的方式只有一种:把模板当作纯 HTML 片段克隆出来,再用 JS 手动设置属性。比如你有一个 <user-card data-name="Alice" data-email="alice@example.com"></user-card>,那就要在自定义元素的 connectedCallback() 里读取这些 dataset,再塞进克隆后的模板节点里。
- 模板必须放在
<body>或<head>中(不能嵌套在其他元素内),否则document.getElementById()可能拿不到 - 不要给
<template>加id以外的属性,比如class或style—— 它们不会生效,还可能干扰后续克隆 - 克隆后记得调用
content.cloneNode(true),否则修改会污染原始模板
自定义元素中读取 dataset 并注入模板内容
Web Components 是目前唯一能自然支持“外部属性 → 模板填充”闭环的原生方案。关键不是模板本身,而是组件类如何桥接二者。
例如声明一个 <product-card>,它从自身属性读值,再填入 <template id="product-tpl"> 的对应位置:
立即学习“前端免费学习笔记(深入)”;
class ProductCard extends HTMLElement {
connectedCallback() {
const name = this.dataset.name || 'Unknown';
const price = this.dataset.price || '—';
const tpl = document.getElementById('product-tpl').content;
const clone = tpl.cloneNode(true);
clone.querySelector('.name').textContent = name;
clone.querySelector('.price').textContent = `¥${price}`;
this.attachShadow({ mode: 'open' }).appendChild(clone);
}
}- 属性名必须用 kebab-case(如
data-product-id),JS 中通过this.dataset.productId访问 - 如果属性值含 HTML(比如富文本描述),用
innerHTML而非textContent,但要确保内容可信,否则有 XSS 风险 - 不要在
constructor里操作 DOM 或读dataset—— 此时元素还没挂载,dataset可能为空
为什么不用 v-html 或 innerHTML 直接拼字符串?
直接拼接字符串看似简单,但会丢失模板的语义化与可维护性。比如:el.innerHTML = `<div class="card">${name}</div>`,一旦结构变复杂,引号嵌套、转义、逻辑分支都会迅速失控。
而 <template> 提供了结构预定义能力:样式、事件绑定、slot 占位符都能提前写死,JS 只负责“填空”。更重要的是,它天然兼容 Shadow DOM —— 你可以在模板里写 <style>,它只会作用于克隆后的子树,不会泄漏到页面全局。
- 模板中避免写内联事件处理器(如
onclick="doSomething()"),改用this.shadowRoot.addEventListener()绑定,否则事件作用域错乱 - 如果要用第三方图标字体(如 Font Awesome),得把
<link>或<style>放进 shadowRoot 内,而不是全局<head> - 模板里用
<slot>处理动态内容区,比硬编码querySelector更健壮,也支持用户自定义插槽内容
跨页面传递配置时,data-* 不是可靠通道
有人试图用 <product-card data-price="99"></product-card> 在 A 页面写入,B 页面靠 JS 读取——这行不通。data-* 属于 DOM 节点实例状态,页面刷新后整个 DOM 树重建,所有 dataset 归零。B 页面根本没那个元素,更谈不上读值。
真要跨页传参,只有三种靠谱路径:
- URL 参数:
?price=99,接收页用new URLSearchParams(window.location.search).get('price') - localStorage:
localStorage.setItem('lastPrice', '99'),另一页读取并手动设到组件上 - 服务端 session:适合登录态关联数据,前端只需发请求取值,不依赖 DOM 属性
模板本身不解决数据持久问题,它只负责“怎么把已有的值画出来”。属性注入的起点,永远是你明确知道数据在哪、怎么拿到它。



















