Web Components 学习分享
Web Components 学习分享
一、Web Components 基础概念
Web Components 是一套用于构建可复用前端 UI 组件的原生浏览器标准,包含三个核心部分:
- Custom Elements:自定义 HTML 标签,定义组件行为与生命周期。
- Shadow DOM:提供 DOM 和样式的封装隔离。
- HTML Templates:定义组件的结构模板。
核心技术
- Custom Elements 负责「定义组件」
- Shadow DOM 负责「封装实现」
- HTML Templates 用于定义组件的结构模板,通常配合
<template>标签使用。模板内容不会直接渲染到页面,而是可通过 JS 克隆并插入到 DOM。
自定义组件及封装
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
div { color: blue; }
</style>
<div>我是内部内容</div>
`;
}
}
customElements.define('my-card', MyCard);
使用 HTML Template
<template id="card-template">
<style>
.card { border: 1px solid #ccc; padding: 8px; border-radius: 4px; }
</style>
<div class="card">
<slot></slot>
</div>
</template>
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const template = document.getElementById('card-template');
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('my-card', MyCard);
使用自定义组件
<my-button></my-button>
二、Web Components 在实际开发中的使用
初始化与组织方式
- 在项目中定义每个组件的类文件。
- 通过
customElements.define()注册标签。 - 在页面中直接使用该标签。
- 若组件间有交互,可通过自定义事件或属性传值
示例代码
1. 定义组件类文件(如 my-button.js):
class MyButton extends HTMLElement {
static get observedAttributes() {
return ['label'];
}
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
button { background: #409eff; color: #fff; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
</style>
<button></button>
`;
this.buttonEl = shadow.querySelector('button');
this.buttonEl.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('clicked', { detail: '按钮被点击', bubbles: true }));
});
}
connectedCallback() {
this._updateLabel();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'label') {
this._updateLabel();
}
}
_updateLabel() {
this.buttonEl.textContent = this.getAttribute('label') || '点击我';
}
}
customElements.define('my-button', MyButton);
2. 在页面中注册并使用组件(带属性传递):
<!-- 引入组件类文件 -->
<script src="./my-button.js"></script>
<!-- 页面中直接使用自定义标签,并传递 label 属性 -->
<my-button label="自定义文案"></my-button>
<script>
document.querySelector('my-button').addEventListener('clicked', e => {
console.log(e.detail); // 输出:按钮被点击
});
</script>
组件逻辑(JS)通常放在组件类内部,或通过模块引入。
三、Web Components 与 iframe 对比
| 对比点 | Web Component | iframe |
|---|---|---|
| 隔离性 | 样式与DOM可局部隔离(Shadow DOM) | 完全隔离(独立文档上下文) |
| 通信 | 自定义事件、属性传递 | postMessage |
| 性能 | 轻量,不需要浏览器多开文档 | 较重,每个 iframe 都是完整渲染上下文 |
| 使用场景 | 微前端、内嵌组件 | 外部系统嵌入、跨域隔离 |
结论:如果只想封装局部 UI 模块,用 Web Component;如果要嵌入完整外部页面或跨域安全隔离,用 iframe。
四、Web Components 与 React / Vue 对比
| 特性 | Web Components | React / Vue |
|---|---|---|
| 响应式数据 | 手动维护 | 内置响应系统 |
| 生命周期 | 原生 DOM 生命周期 | 框架生命周期 |
| 样式作用域 | Shadow DOM 隔离 | scoped / CSS Modules |
| 虚拟 DOM | 无(浏览器原生) | 有(框架管理) |
| 跨框架使用 | ✅ 支持 | ❌ 限制在框架内 |
总结:
- 如果希望组件跨框架可复用 → Web Component
- 如果主要在 React/Vue 内部开发 → 用框架原生组件更方便
五、在 Vue 中使用 Web Components
Vue 从 2.6+ / Vue 3 开始原生支持 Web Components。
引入使用
import './my-button.js';
createApp(App).mount('#app');
<template>
<my-button @clicked="handleClick"></my-button>
</template>
<script setup>
function handleClick(e) {
console.log('web component 点击', e.detail);
}
</script>
注意点
- 传递对象需通过 ref 设置 property。
- 事件名建议小写(Vue 会转小写)。
- 插槽 slot 可直接使用。
六、在 React 中使用 Web Components
React 视自定义元素为普通 DOM 标签。
示例
import React, { useRef, useEffect } from 'react';
import './my-counter.js';
export default function App() {
const ref = useRef();
useEffect(() => {
const el = ref.current;
const handler = e => console.log(e.detail);
el.addEventListener('count-changed', handler);
return () => el.removeEventListener('count-changed', handler);
}, []);
return <my-counter ref={ref}></my-counter>;
}
注意事项
- 必须用
addEventListener监听事件(React 不支持 CustomEvent)。 - 复杂属性需通过 ref 设置 property。
- 插槽可正常渲染。
推荐封装
将 Web Component 封装成 React 组件(Wrapper),以简化使用体验。
七、常用 Web Components 融合库
| 库名 | 作用 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| @r2wc/react-to-web-component | React → Web Component | 跨框架复用 React 组件 | 简单直接 | 运行时封装层 |
| @lit/react | Web Component → React | 在 React 中使用 Lit / WC | 自动事件 & 属性映射 | 有 wrapper 成本 |
| vue-web-component-wrapper | Vue2 → Web Component | 把 Vue2 组件导出成 WC | 官方出品 | Vue3 不完全兼容 |
| Vue defineCustomElement | Vue3 原生 API | Vue3 直接输出 WC | 原生支持 | 无法在 React 内直接绑定响应式 |
八、结论
- ✅ 如果目标是跨框架复用组件 → 使用 Web Component。
- ✅ 如果项目以 Vue/React 为核心 → 在框架内部使用其组件体系更高效。
- ⚙️ 结合场景:可以用 Web Component 封装通用模块,在不同框架应用中引入。
- 🧰 生态工具:@lit/react、r2wc、vue-web-component-wrapper 等可帮助桥接差异。
九、兼容性
一、Web Components 的 4 大核心规范
| 模块 | 功能 | 标准状态 |
| ——————- | —————————————- | —— |
| Custom Elements | 定义自定义 HTML 元素(customElements.define()) | ✅ 已标准化 |
| Shadow DOM | 样式和 DOM 封装(attachShadow()) | ✅ 已标准化 |
| HTML Templates | <template> 定义可复用 DOM 片段 | ✅ 已标准化 |
| ES Modules | 模块化加载组件脚本 | ✅ 已标准化 |
二、浏览器兼容性总览(截至 2025)
| 浏览器 | 版本支持情况 | 备注 |
|---|---|---|
| Chrome | ✅ 完全支持(≥ v54) | 原生支持所有特性 |
| Edge (Chromium) | ✅ 完全支持(≥ v79) | 与 Chrome 一致 |
| Firefox | ✅ 完全支持(≥ v63) | 曾有 Shadow DOM 性能问题,已优化 |
| Safari (macOS/iOS) | ✅ 支持(≥ v10.1) | 现版本已较稳定,但仍有小 bug(如 slot 样式继承) |
| Opera | ✅ 完全支持 | Chromium 内核 |
| Samsung Internet / Android Chrome | ✅ 支持良好 | 与 Chromium 一致 |
| 老版 Edge (EdgeHTML) | ❌ 不支持 | 已淘汰 |
| IE11 / IE 系列 | ❌ 不支持 | 只能靠 polyfill |
结论:现代浏览器(Chrome / Edge / Firefox / Safari / Opera)全部原生支持。 只有 IE11 和老 Edge 需要 polyfill。
实际业务场景
商业分析助手,大模型生成图表嵌入实现分析
现状
- 服务端返回 完整 HTML 页面字符串,包含html,head,style,script 标签。
- 当前方案:动态写入 iframe.contentDocument, 还可以
<iframe srcdoc="...">方式引入
使用 web component 实现分析
在大多数场景下,Web Component 性能显著优于 iframe,但它们的隔离级别和应用边界不同,不能简单替代。
一、底层机制差异
| 对比项 | Web Component | iframe |
|---|---|---|
| 渲染上下文 | 共享同一个浏览器上下文(同一 DOM、同一 JS 引擎实例) | 独立浏览器上下文(独立 DOM 树、独立 JS 引擎实例) |
| 样式隔离 | Shadow DOM 局部隔离 | 完全隔离(独立 CSSOM) |
| JS 运行环境 | 共享主页面 JS 线程 | 独立 JS 引擎、独立全局对象 |
| 通信方式 | 属性、事件、方法直接交互 | postMessage(跨文档消息) |
| 内存消耗 | 轻量(共用主线程) | 重(每个 iframe 相当于一个迷浏览器) |
| 安全隔离 | 弱(主页面能直接访问子组件) | 强(完全隔离) |
二、性能对比分析
- 启动开销
- iframe:浏览器要为每个 iframe 启动新的文档加载、解析 HTML/CSS/JS,几乎等同于打开一个独立页面。
- Web Component:仅需创建自定义元素、插入 DOM,属于同一个页面上下文。
Web Component 启动性能高数十倍以上,特别是在多实例场景(几十个子模块)时。
- 内存与线程开销
- iframe 会分配独立的内存空间(含 JS 引擎堆栈、样式树、布局树等)。
- Web Component 共用宿主页面的渲染和执行上下文,仅有少量 DOM 和样式节点。
Web Component 更节省内存,浏览器资源占用低
- 通信性能
- iframe → 主页面通信需 postMessage(异步、序列化),性能损耗明显。
- Web Component → 可通过属性或事件同步通信(无需跨上下文)。
Web Component 的交互通信延迟更低
-
隔离成本与风险
Web Component 的隔离是“逻辑层面的”:
- Shadow DOM 隔离样式,但 JS 仍可直接访问组件内容。
- 如果组件中执行外部脚本(尤其是服务端注入 HTML + JS),会共享宿主页面的执行上下文,存在 XSS、污染全局变量的风险。
而 iframe:自带 安全沙箱(sandbox)可以彻底防止子页面修改主页面环境。
所以如果的注入内容是第三方服务端生成的整页 HTML + JS, iframe 安全性高得多, Web Component 反而危险(可能污染宿主环境)。
三、适用场景建议
| 目标 | 推荐技术 |
|---|---|
| 嵌入完全独立、第三方生成的完整页面 | ✅ iframe(安全、隔离) |
| 嵌入受控的模块内容(自己生成) | ✅ Web Component(轻量高效) |
| 需要跨系统安全通信(跨域) | ✅ iframe + postMessage |
| 内嵌组件要与宿主紧密交互 | ✅ Web Component |
| 微前端架构 | 可根据需要混合使用(见下) |
性能对比
| 指标 | iframe | Web Component |
|---|---|---|
| 初始加载时间 | ≈ 800~1200ms × 10 | ≈ 50~100ms × 10 |
| 内存占用 | 高(每个 20~50MB) | 低(共享上下文) |
| 通信延迟 | ~3–5ms(异步) | <1ms(同步) |
| 安全隔离 | 强 | 弱 |
| 样式隔离 | 强 | 中等(Shadow DOM) |
结论总结
| 维度 | 推荐 |
|---|---|
| 性能 | ✅ Web Component 优势明显 |
| 隔离性 / 安全性 | ✅ iframe 胜出 |
| 开发灵活性 | ✅ Web Component |
| 加载完整页面(含 JS/CSS) | ⚠️ iframe(WC 无法安全执行外部 JS) |
| 嵌入同域、自控页面 | ✅ Web Component 最优 |
- Web Component 性能更优,但安全性与隔离不如 iframe。
- 如果注入内容是外部系统输出的完整 HTML + JS 页面,仍建议使用 iframe。
- 若是控制的组件或模块内容,Web Component 是更现代、更高效的替代方案。