

在 React、Vue、Angular 三分天下的今天,组件化开发早已深入人心。但你是否遇到过这样的场景:公司内部既有 Vue2/3 项目,也有 React 项目,还需要维护一些 jQuery 老系统。为了保持 UI 风格统一,难道要为每个框架都写一套组件库吗?
Web Components 提供了一种标准化的解决方案:它允许你创建可重用的自定义元素,这些元素在任何框架中都能像原生 HTML 标签一样工作。本文将带你从零实现一个 Web Component,并探讨其在微前端与跨框架场景下的应用。
connectedCallback(挂载)、disconnectedCallback(卸载)、attributeChangedCallback(属性变化)。<my-btn>,在 React 中需处理自定义事件的兼容性。我们来实现一个 <user-card> 组件,支持 avatar 和 name 属性,且样式不被外部污染。
// 1. 定义 HTML 模板
const template = document.createElement('template');
template.innerHTML = `
<style>
.card {
display: flex;
align-items: center;
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
}
img {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 16px;
}
h3 { margin: 0; font-size: 18px; }
</style>
<div class="card">
<img />
<div>
<h3></h3>
<slot name="desc"></slot> <!-- 插槽支持 -->
</div>
</div>
`;
// 2. 创建自定义类
class UserCard extends HTMLElement {
constructor() {
super();
// 开启 Shadow DOM,实现样式隔离
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
// 3. 监听属性变化
static get observedAttributes() {
return ['avatar', 'name'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'avatar') {
this.shadowRoot.querySelector('img').src = newValue;
} else if (name === 'name') {
this.shadowRoot.querySelector('h3').innerText = newValue;
}
}
}
// 4. 注册组件
window.customElements.define('user-card', UserCard);无论是在 React、Vue 还是纯 HTML 中,都可以直接这样用:
<user-card avatar="https://api.uomg.com/api/rand.avatar" name="Fruge">
<p slot="desc">Senior Frontend Developer</p>
</user-card>在 Web Components 之前,为了防止样式冲突,我们不得不使用 BEM 命名规范或 CSS Modules。Shadow DOM 从浏览器层面解决了这个问题:
Web Components 的插槽机制与 Vue 非常相似(事实上 Vue 的插槽设计灵感正来源于此):
<slot>:默认插槽。<slot name="xxx">:具名插槽。裸写原生 API 比较繁琐(如手动 diff 更新 DOM)。Google 推出的 Lit 库(前身是 Polymer)极大地简化了这一过程,它基于 lit-html 渲染引擎,体积极小。
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('simple-counter')
export class SimpleCounter extends LitElement {
static styles = css`
button { color: blue; }
`;
@property({ type: Number })
count = 0;
render() {
return html`
<p>Count: ${this.count}</p>
<button @click="${this._increment}">+</button>
`;
}
private _increment() {
this.count++;
// 触发自定义事件
this.dispatchEvent(new CustomEvent('count-changed', {
detail: this.count,
bubbles: true,
composed: true // 允许穿透 Shadow DOM
}));
}
}Vue 对 Web Components 的支持非常完美。
配置:在 Vite/Webpack 中配置 compilerOptions.isCustomElement,告诉 Vue 编译器哪些标签是自定义元素,不要报错。
绑定:
<simple-counter :count="count" @count-changed="handleCount" />React 在 19 之前对 Web Components 的支持略显尴尬,主要在于事件系统和属性传递。
ref 手动赋值。useRef + addEventListener。Web Components 不是为了取代 React/Vue,而是为了补充组件互操作性的缺失。在构建设计系统(Design System)或跨框架微前端应用时,它是一个极具前瞻性的选择。