学完这篇,你能搞清楚 <template> 和 <slot> 这两个原生标签各自解决什么问题,并且能照着代码在没有框架的项目里做出可复用的 DOM 片段和带插槽的自定义组件。
第一步:先分清这两个标签管什么
<template> 管的是"这段 HTML 先别渲染,等我需要时再克隆出来用"。写在里面的内容不进文档树、不加载图片、不执行脚本、不触发任何网络请求。
<slot> 管的是"组件留个口子,让使用方把内容塞进来"。它只在 Shadow DOM 或自定义元素里有效。
一句话记:template 是复用结构,slot 是复用外壳、内容由外部决定。
注意:直接在普通 HTML 里写 <slot> 是没有任何效果的,必须配合 attachShadow() 或自定义元素使用。
第二步:用 template 渲染列表
这是最常见的场景——不要再用字符串拼 innerHTML。
<template id="row-tpl">
<li class="row">
<span class="name"></span>
<em class="time"></em>
</li>
</template>
<ul id="list"></ul>
const tpl = document.getElementById('row-tpl');
const list = document.getElementById('list');
const frag = document.createDocumentFragment();
data.forEach(item => {
const node = tpl.content.cloneNode(true); // 关键:true
node.querySelector('.name').textContent = item.name;
node.querySelector('.time').textContent = item.time;
frag.appendChild(node);
});
list.appendChild(frag); // 一次性插入,只触发一次重排
要点有两个:一是必须走 tpl.content,二是 cloneNode(true) 的参数不能省。
注意:document.querySelector('.name') 找不到模板里的元素,因为模板内容不属于文档树。所有查找都要从 tpl.content 出发。
注意:cloneNode() 不加 true 只能克隆空壳,子节点全丢,这是新手最常踩的坑。
第三步:把 template 当"待激活容器"
弹窗、编辑表单、只在点击后才出现的重内容,都可以提前写成模板,需要时再克隆进页面。
好处是页面首屏更快:模板里的图片不会预加载。
注意:这同时也是缺点——用户点开弹窗的那一刻才开始加载图片,视觉上会闪一下。内容重的弹窗建议做一次预加载,或者只对交互后才出现的内容用模板。
第四步:给自定义元素开插槽
class UserCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.card { border: 1px solid #ddd; padding: 12px; border-radius: 8px; }
::slotted(.name) { font-weight: bold; }
</style>
<div class="card">
<div class="hd"><slot name="title">默认标题</slot></div>
<div class="bd"><slot></slot></div>
</div>`;
}
}
customElements.define('user-card', UserCard);
使用方式:
<user-card>
<span class="name" slot="title">张三</span>
<p>这段内容会进默认插槽。</p>
</user-card>
三种形态一次说清:
- 默认插槽:
<slot></slot>,接收所有没写 slot 属性的子元素
- 具名插槽:
<slot name="title">,接收 slot="title" 的元素
- 回退内容:
<slot name="title">默认标题</slot>,外部不传时就用标签之间的内容
注意:::slotted() 只能选中插槽的一级子元素,不能向下穿透。想控制更深层的样式,得由外部传样式进去,或者改用 CSS 变量。
注意:外部传进来的元素,样式由外部文档决定;shadow 里的样式默认管不到它。这是 Shadow DOM 的样式隔离,不是 bug。
第五步:什么时候别用
template + slot 适合"结构固定、内容可变"的复用,比如列表项、卡片、弹窗壳子。如果你的组件内部还有大量交互状态切换,手写事件绑定和状态同步的代码会很快变多,这时候更该考虑成熟框架。
另外 slot 不支持 IE,如果项目还要兼容 IE 就别走这条路。
小结
<template> 里的内容不渲染、不请求资源,靠 tpl.content.cloneNode(true) 取出来用
- 查找模板内元素必须从
tpl.content 出发,直接 document.querySelector 查不到
- 批量插入用
DocumentFragment,只触发一次重排
<slot> 只在 Shadow DOM / 自定义元素中生效,分默认插槽、具名插槽、回退内容三种写法
::slotted() 不能穿透多层,外部元素样式归外部管
- 组件内部交互复杂时,别硬扛,该上框架就上框架