Web Components 是浏览器原生支持的一套技术标准,用于创建可复用、封装性强的自定义 HTML 元素 。开发者可以像使用 <div>、<span> 等原生标签一样使用这些自定义元素,且它们的样式和行为不会与页面其他部分冲突,极大提升了代码复用性和维护性。
核心技术组成
Web Components 由以下 4 个核心技术组成,共同实现组件的定义、封装和复用:
- Custom Elements(自定义元素) 允许开发者定义新的 HTML 标签(如
<my-button>、<user-card>),并关联自定义的行为和属性。 - Shadow DOM(影子 DOM) 为自定义元素创建一个 "隔离的 DOM 树",内部的样式和结构不会影响外部,外部样式也不会污染内部(解决样式冲突问题)。
- HTML Templates(HTML 模板) 通过
<template>标签定义组件的结构(不会默认渲染到页面),需要时再动态实例化,避免冗余代码。 - Slots(插槽) 允许在自定义元素内部预留 "占位符",外部使用组件时可以传入自定义内容(类似 React 的
children),增强组件灵活性。
如何使用 Web Components?
步骤 1:定义自定义元素(Custom Elements)
使用 customElements.define() 方法注册自定义元素,需指定标签名(必须包含连字符,如 user-card,避免与原生标签冲突)和组件类(继承自 HTMLElement 或原生元素)。
js
// 定义组件类
class UserCard extends HTMLElement {
constructor() {
super(); // 必须调用父类构造函数
// 后续初始化逻辑(如创建 Shadow DOM、绑定模板等)
}
}
// 注册自定义元素:标签名是 <user-card>
customElements.define('user-card', UserCard);
步骤 2:创建 Shadow DOM(实现封装)
通过 attachShadow() 方法为组件创建 Shadow DOM,实现样式和结构的隔离。mode: 'open' 表示外部可通过 shadowRoot 访问内部 DOM;mode: 'closed' 则完全隔离(不推荐,灵活性低)。
步骤 3:结合 Template 和 Slot(定义结构和可定制内容)
使用 <template> 定义组件的固定结构,通过 <slot> 预留可替换的内容(如用户头像、姓名等)。
html
<!-- 定义模板(不会默认渲染) -->
<template id="user-card-template">
<style>
/* 样式仅作用于 Shadow DOM 内部 */
.card {
border: 1px solid #eee;
padding: 16px;
border-radius: 8px;
max-width: 300px;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
}
.name {
font-weight: bold;
margin: 8px 0;
}
</style>
<div class="card">
<!-- 插槽:外部可传入头像图片 -->
<img class="avatar" slot="avatar" alt="Avatar">
<!-- 插槽:外部可传入姓名 -->
<div class="name"><slot name="name">默认姓名</slot></div>
<!-- 插槽:外部可传入简介(默认插槽,无需命名) -->
<p><slot>默认简介</slot></p>
</div>
</template>
在组件类中,将模板内容克隆到 Shadow DOM 中:
js
class UserCard extends HTMLElement {
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: 'open' });
// 获取模板并克隆内容
const template = document.getElementById('user-card-template');
const templateContent = template.content.cloneNode(true);
// 将克隆的内容添加到 Shadow DOM
this.shadowRoot.appendChild(templateContent);
}
}
步骤 4:使用自定义元素
在页面中直接使用 <user-card> 标签,并通过 slot 属性传入内容:
html
<!-- 使用自定义组件 -->
<user-card>
<!-- 对应模板中 name="avatar" 的插槽 -->
<img slot="avatar" src="user.jpg" alt="User Avatar">
<!-- 对应模板中 name="name" 的插槽 -->
<span slot="name">张三</span>
<!-- 对应默认插槽(无 name 属性) -->
前端工程师,喜欢探索 Web 新技术。
</user-card>
此时页面会渲染出一个带样式的用户卡片,且内部样式不会影响页面其他元素。
步骤 5:利用生命周期回调(增强交互)
自定义元素提供了生命周期回调函数,可在特定阶段执行逻辑(如初始化、属性变化、挂载 / 卸载等):
connectedCallback:元素挂载到 DOM 时触发(适合初始化事件监听、数据请求)。disconnectedCallback:元素从 DOM 移除时触发(适合清理事件监听、定时器)。attributeChangedCallback(attrName, oldVal, newVal):监听的属性变化时触发(需配合observedAttributes声明监听的属性)。
示例:监听 theme 属性变化,动态修改卡片样式:
js
class UserCard extends HTMLElement {
// 声明需要监听的属性
static get observedAttributes() {
return ['theme'];
}
// 属性变化时触发
attributeChangedCallback(attrName, oldVal, newVal) {
if (attrName === 'theme' && newVal === 'dark') {
this.shadowRoot.querySelector('.card').style.backgroundColor = '#333';
this.shadowRoot.querySelector('.card').style.color = 'white';
}
}
// 元素挂载到 DOM 时触发
connectedCallback() {
console.log('user-card 已挂载');
}
}
使用时通过属性传入主题:
html
<user-card theme="dark">...</user-card>
优势总结
- 原生支持:无需依赖任何框架,浏览器直接运行。
- 跨框架兼容:可在 React、Vue、Angular 等任意框架中使用。
- 封装性强:Shadow DOM 隔离样式和结构,避免冲突。
- 复用性高:一次定义,多处使用,降低代码冗余。
兼容性
现代浏览器(Chrome、Firefox、Edge、Safari 10+)均支持 Web Components 核心特性,如需兼容旧浏览器(如 IE),可使用 polyfill 补充支持。