Web Components 与 Shadow DOM 实战
React 写的组件不能在 Vue 里用,Vue 组件也不能在原生 HTML 里跑。Web Components 是浏览器原生的组件标准,写一次可以在任何框架甚至不用框架的页面里直接用。本文从自定义元素到 Shadow DOM,讲清这套原生组件方案。
一、什么是 Web Components
Web Components 不是一个单一技术,而是浏览器原生提供的一组 API:自定义元素(Custom Elements)、Shadow DOM、HTML 模板(Template)。它们合起来让你可以创建自己的 HTML 标签,比如 <user-card name="小明"></user-card>,而且这个标签的样式和 DOM 是封装隔离的,不会被外部页面影响。
| 技术 | 作用 |
|---|---|
| Custom Elements | 定义自定义 HTML 标签及其行为 |
| Shadow DOM | 封装组件内部 DOM,外部 CSS 进不去 |
| HTML Template | 声明组件的 HTML 结构模板 |
二、第一个自定义元素
class GreetingCard extends HTMLElement {
constructor() {
super()
// 创建 Shadow DOM 隔离
const shadow = this.attachShadow({ mode: 'open' })
const name = this.getAttribute('name') || '朋友'
const style = document.createElement('style')
style.textContent = `
.card {
background: #f0f7ff;
border: 1px solid #1e5fa8;
border-radius: 8px;
padding: 16px;
font-family: sans-serif;
}
`
const div = document.createElement('div')
div.className = 'card'
div.textContent = `你好,${name}!欢迎使用 Web Components`
shadow.appendChild(style)
shadow.appendChild(div)
}
}
// 注册自定义元素:标签名必须带短横线
customElements.define('greeting-card', GreetingCard)
<!-- 在页面中直接使用 -->
<greeting-card name="小红"></greeting-card>
<greeting-card name="小刚"></greeting-card>
greeting-card),避免和原生 HTML 标签冲突。attachShadow({mode:'open'}) 开启 Shadow DOM,组件内部样式和外部页面完全隔离。
三、Shadow DOM 的封装力
没有 Shadow DOM 时,你写的组件样式会泄漏到页面其他地方,页面样式也会影响组件。Shadow DOM 把组件内部变成一个独立的 DOM 树,外部 CSS 选择器穿不透。
// 组件内部的样式
style.textContent = `
p { color: red; } // 只影响组件内部的 p 标签
`
// 外部页面的样式
// p { color: blue; } // 不会影响 Shadow DOM 内部的 p 标签
这就是 Web Components 能跨框架复用的核心原因:样式和 DOM 都是封装好的,扔到任何页面里都不会打架。
四、属性与响应式
class UserCard extends HTMLElement {
static get observedAttributes() {
return ['name', 'avatar'] // 监听哪些属性变化
}
attributeChangedCallback(name, oldValue, newValue) {
this.render()
}
connectedCallback() {
this.render()
}
render() {
const shadow = this.attachShadow({ mode: 'open' })
const name = this.getAttribute('name') || '匿名'
shadow.innerHTML = `
<div class="user">
<img src="${this.getAttribute('avatar') || '/default.png'}">
<span>${name}</span>
</div>
`
}
}
customElements.define('user-card', UserCard)
observedAttributes 声明要监听的属性,属性变化时自动调用 attributeChangedCallback。
五、在框架中使用
// React 中使用 Web Components
function App() {
return (
<div>
<user-card name="小明" avatar="/me.png"></user-card>
</div>
)
}
// Vue 中使用 Web Components
<template>
<user-card name="小红" avatar="/her.png"></user-card>
</template>
// 原生 HTML 中直接用
<user-card name="小刚"></user-card>
Web Components 就是普通 HTML 标签,任何框架都能识别。这就是它相比框架专属组件的最大优势:一次开发,到处运行。
六、什么时候用 Web Components
适合用的场景:设计系统组件库(按钮、卡片、弹窗)、需要跨多个项目或团队复用的组件、微前端架构中的独立子应用。
不适合的场景:重度交互的复杂应用(状态管理不如 React/Vue 方便)、需要极致渲染性能的列表(虚拟滚动还是框架更成熟)。
七、总结
Web Components 是浏览器原生的组件方案,Shadow DOM 提供了样式和 DOM 的封装隔离,自定义元素让你定义自己的 HTML 标签。它的最大价值是跨框架复用:写一个 <user-card>,React 项目能用,Vue 项目能用,甚至纯 HTML 页面也能直接用。对于设计系统和跨团队共享组件,Web Components 是值得投入的方向。