HTML 模板元素 template 和 slot 怎么配合 Web Components 使用
结论:<template> 负责「把一段不会立即渲染的 DOM 存起来」,<slot> 负责「在 Shadow DOM 里留一个口子让外部内容插进来」;两者配合的标准姿势是——把 <template> 里的结构连同 <slot> 一起克隆进 shadowRoot,外部使用者往自定义元素标签里写带 slot="名字" 的子节点即可完成内容分发。
template 和 slot 的分工是什么?
<template> 的 content 是一个 DocumentFragment,它里面的节点不会被渲染、图片不会加载、<script> 不会执行,只有被克隆并插入文档后才生效——这正是它适合当组件「模具」的原因。<slot> 是 Shadow DOM 的插槽占位符,浏览器会把宿主元素(light DOM)的顶层子节点「分配」到同名插槽的位置上,节点本身仍留在 light DOM 里。
结论:template 解决「结构从哪来」,slot 解决「外部内容往哪放」,二者的接口就是 name 属性和 slot="xxx" 属性的一一对应。
怎么用 template + slot 写一个自定义元素?
完整步骤分三步:定义模板 → 类里克隆模板 → 外部传插槽内容。
<template id="user-card">
<style>
:host { display: block; border: 1px solid #ddd; padding: 12px; }
::slotted([slot="name"]) { font-weight: 700; }
</style>
<h3><slot name="name">匿名用户</slot></h3>
<slot name="avatar"></slot>
<p><slot>暂无简介</slot></p>
</template>
<user-card>
<span slot="name">张三</span>
<p>前端工程师,关注 Web Components。</p>
</user-card>
class UserCard extends HTMLElement {
constructor() {
super();
const tpl = document.getElementById('user-card');
this.attachShadow({ mode: 'open' })
.appendChild(tpl.content.cloneNode(true)); // 必须 clone
}
}
customElements.define('user-card', UserCard); // 标签名必须含连字符
注意三点:customElements.define 的标签名必须包含连字符,否则直接抛错;attachShadow 必须在克隆之前调用;mode: 'open' 时 this.shadowRoot 可访问,'closed' 时为 null。
没有对应插槽的节点会怎样?默认内容什么时候显示?
结论:带 slot="xxx" 但找不到同名插槽的节点,以及没有 slot 属性又没有默认插槽的节点,都不会被渲染(仍存在于 DOM 中,但不显示)。<slot name="name">匿名用户</slot> 里的文字是「兜底内容」,只有在没有任何节点分配到该插槽时才显示。
监听分配变化用 slotchange 事件,挂在 <slot> 元素上,首次分配时也会触发一次:
this.shadowRoot.querySelector('slot[name="name"]')
.addEventListener('slotchange', e => {
console.log(e.target.assignedNodes({ flatten: true }).length);
});
反向查询用 element.assignedSlot 拿到该节点被分配到的插槽。
用 ::slotted() 给插槽内容写样式要注意什么?
结论:::slotted() 只能选中被分配到插槽的一级节点,不能穿透到孙节点,也不能选中组件内部的 shadow 节点。::slotted([slot="name"]) 这种属性选择器写法是常用形式。
原因是插槽内容的所有权属于外部文档,Shadow DOM 的样式作用域默认管不到它。但有一点例外:继承属性会穿透,比如 color、font-family、line-height,所以在 :host 上设置这些属性,插槽内容会跟着变。想整体统一外观,优先用继承属性而不是 ::slotted() 逐个写。
最常踩的三个坑
第一,忘记 cloneNode(true):直接把 tpl.content 塞进去会把内容「搬走」,同一个模板第二次使用就拿到空节点。第二,slot 名字拼错或大小写不一致,节点静默消失、控制台没有任何报错,排查时先看 assignedNodes() 的返回长度。第三,在 constructor 里读写 light DOM 子节点,此时子节点可能还没解析完,需要访问子节点就在 connectedCallback 或 slotchange 里做。
如果要在服务端直接输出带 Shadow DOM 的 HTML,可以用声明式 Shadow DOM:把根模板写成 <template shadowrootmode="open">,Chrome 111+、Safari 16.4+、Firefox 123+ 支持,浏览器解析时自动挂载,省掉一次 JS 调用。
总结一句:template 提供不渲染的模具,slot 提供内容入口,cloneNode(true) + 名字对齐 + ::slotted() 只管一层,这三条记住就能把 Web Components 的封装和定制同时握住。
原文链接:https://www.gj0.com/thread-801.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。