📦 Web Components 系统化语法与用例手册
一、概述
Web Components 是一组浏览器原生的 API 规范,支持创建可重用、封装良好的 UI 组件,而无需依赖框架。
Web Components 包括 3 个核心规范:
- Custom Elements – 自定义 HTML 元素的声明与生命周期
- Shadow DOM – 样式/结构封装的 DOM 子树
- HTML Templates – 可复用的模板结构
兼容性良好,现代浏览器均已原生支持。
二、Custom Elements 自定义元素
✨ 语法结构
class MyElement extends HTMLElement {
// 生命周期回调
connectedCallback() {}
disconnectedCallback() {}
adoptedCallback() {}
attributeChangedCallback(name, oldVal, newVal) {}
// 属性监听列表
static get observedAttributes() {
return ['title', 'active']
}
// DOM property getter/setter
get title() {}
set title(value) {}
}
// 注册元素名(必须含有中划线)
customElements.define('my-element', MyElement)
✅ 用例示例
<my-element title="Hello" active></my-element>
const el = document.querySelector('my-element')
el.title = 'World' // 设置 DOM property
el.setAttribute('title', 'X') // 设置 HTML attribute(会触发反射)
三、Shadow DOM 封装
✨ 语法结构
class MyShadowEl extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' }) // or "closed"
this.shadowRoot.innerHTML = `
<style>
:host([active]) { color: red; }
</style>
<slot></slot>
`
}
}
customElements.define('my-shadow', MyShadowEl)
✅ 用例示例
<my-shadow active>Hello</my-shadow>
特点:
- 内部样式隔离
- 外部不可选中内部元素
:host
表示宿主元素本身
四、HTML Templates
✨ 语法结构
<template id="card-template">
<style>
.card { border: 1px solid #ccc; }
</style>
<div class="card">
<slot name="content"></slot>
</div>
</template>
✅ 用例示例
class CardBox extends HTMLElement {
constructor() {
super()
const template = document.getElementById('card-template')
const clone = template.content.cloneNode(true)
this.attachShadow({ mode: 'open' }).appendChild(clone)
}
}
customElements.define('card-box', CardBox)
<card-box>
<div slot="content">Hello in a Card!</div>
</card-box>
五、Property ↔ Attribute 映射与反射
🌐 映射关系机制
类型 | HTML Attribute | DOM Property |
---|---|---|
字符串 | <el title="abc"> | el.title = 'abc' |
布尔值 | <el active> / not exist | el.active = true/false |
对象/数组 | ❌ 无法表示 | ✅ 可通过 property |
🧩 手动反射机制(推荐)
get active() {
return this.hasAttribute('active')
}
set active(val) {
if (val) {
this.setAttribute('active', '')
} else {
this.removeAttribute('active')
}
}
否则,框架(如 Vue/React)无法正确传递状态。
六、生命周期钩子(custom element callbacks)
方法名 | 触发时机 |
---|---|
constructor() | 实例化时 |
connectedCallback() | 插入 DOM 时 |
disconnectedCallback() | 移出 DOM 时 |
adoptedCallback() | 被移动到新文档时 |
attributeChangedCallback(name, oldVal, newVal) | 监听的属性变化时 |
七、事件派发 & 双向通信
✨ 组件内派发事件
this.dispatchEvent(new CustomEvent('custom-click', {
detail: { x: 1 },
bubbles: true,
composed: true,
}))
✅ 外部监听事件
<my-element></my-element>
<script>
document.querySelector('my-element').addEventListener('custom-click', e => {
console.log(e.detail)
})
</script>
八、框架集成注意事项(以 Vue 为例)
框架绑定值 | 结果 | 说明 |
---|---|---|
:title="msg" | 设置为 attribute,除非 prop 存在 | Vue 检查 prop in el 决定绑定方式 |
:title.prop="msg" | 强制设置为 DOM property | 用于传递对象、布尔值、函数等复杂值 |
✅ 推荐做法(以布尔值为例)
<my-toggle :checked.prop="true" />
否则:
<my-toggle checked="false" /> <!-- 实际为 true -->
九、最佳实践建议
- 使用
observedAttributes
+attributeChangedCallback
响应外部变更 - 为每个属性写明 getter/setter,并处理 reflect
- 使用
slot
和<template>
提高可插拔性 - 样式封装时配合
:host
,::slotted
,:host-context
CustomEvent
+dispatchEvent
实现向上传递
十、结语
Web Components 为组件封装提供了浏览器级标准方案,其核心优势是:
- 原生支持,无框架依赖
- 可与任意框架集成(Vue、React、Svelte)
- 在设计系统、微前端、SDK 嵌入场景中具备长期生命力
在现代应用中,可以将 Web Components 视为“技术内核”或“平台桥梁”,作为可复用 UI 模块的标准载体。