Web Components 封装实战:打造可复用的跨框架组件

Web Components 封装实战:打造可复用的跨框架组件

在 React、Vue、Angular 三分天下的今天,组件化开发早已深入人心。但你是否遇到过这样的场景:公司内部既有 Vue2/3 项目,也有 React 项目,还需要维护一些 jQuery 老系统。为了保持 UI 风格统一,难道要为每个框架都写一套组件库吗?

Web Components 提供了一种标准化的解决方案:它允许你创建可重用的自定义元素,这些元素在任何框架中都能像原生 HTML 标签一样工作。本文将带你从零实现一个 Web Component,并探讨其在微前端与跨框架场景下的应用。

TL;DR

  • 核心三剑客:Custom Elements(自定义标签)、Shadow DOM(样式隔离)、HTML Templates(结构复用)。
  • 生命周期connectedCallback(挂载)、disconnectedCallback(卸载)、attributeChangedCallback(属性变化)。
  • 跨框架 :在 Vue 中直接用 <my-btn>,在 React 中需处理自定义事件的兼容性。
  • 最佳实践 :推荐使用 Lit 等轻量库简化开发,而非裸写原生 API。

1. 原生实现:手写一个 UserCard 组件

我们来实现一个 <user-card> 组件,支持 avatarname 属性,且样式不被外部污染。

javascript 复制代码
// 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 中,都可以直接这样用:

html 复制代码
<user-card avatar="https://api.uomg.com/api/rand.avatar" name="Fruge">
  <p slot="desc">Senior Frontend Developer</p>
</user-card>

2. 核心技术深度解析

Shadow DOM:真正的样式隔离

在 Web Components 之前,为了防止样式冲突,我们不得不使用 BEM 命名规范或 CSS Modules。Shadow DOM 从浏览器层面解决了这个问题:

  • 外部进不去:全局 CSS 无法影响 Shadow Root 内部(除了 CSS 变量)。
  • 内部出不来:组件内的样式也不会污染全局。

Slot 插槽机制

Web Components 的插槽机制与 Vue 非常相似(事实上 Vue 的插槽设计灵感正来源于此):

  • <slot>:默认插槽。
  • <slot name="xxx">:具名插槽。

3. 进阶:使用 Lit 简化开发

裸写原生 API 比较繁琐(如手动 diff 更新 DOM)。Google 推出的 Lit 库(前身是 Polymer)极大地简化了这一过程,它基于 lit-html 渲染引擎,体积极小。

typescript 复制代码
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
    }));
  }
}

4. 框架集成指南

Vue (2.x / 3.x)

Vue 对 Web Components 的支持非常完美。

  • 配置 :在 Vite/Webpack 中配置 compilerOptions.isCustomElement,告诉 Vue 编译器哪些标签是自定义元素,不要报错。

  • 绑定

    html 复制代码
    <simple-counter :count="count" @count-changed="handleCount" />

React (16/17/18)

React 在 19 之前对 Web Components 的支持略显尴尬,主要在于事件系统属性传递

  • 属性 :React 会将所有 props 当作 HTML Attribute(字符串)传递,无法传递复杂对象(数组/对象)。需使用 ref 手动赋值。
  • 事件 :React 的合成事件系统无法监听 Web Component 的自定义事件。需使用 useRef + addEventListener
  • React 19+:已全面改善对 Web Components 的支持,上述问题基本解决。

5. 适用场景与局限性

适用场景

  1. 跨技术栈组件库:公司内部有多种技术栈,需要统一的 UI 基础组件(Button, Icon, Input)。
  2. 微前端:在微前端架构中,子应用可能使用不同框架,Web Components 是天然的隔离边界。
  3. 嵌入式微件:如聊天窗口、反馈按钮,可直接嵌入任意第三方网站。

局限性

  1. SSR 支持:Shadow DOM 的服务端渲染(SSR)目前仍处于实验阶段(Declarative Shadow DOM)。
  2. 样式定制:由于强隔离,外部很难通过 CSS 覆盖内部样式,通常需要组件暴露大量的 CSS Variables 供外部调整。
  3. 无障碍性 (A11y):需要手动处理 Shadow DOM 内部的焦点管理和 ARIA 属性。

6. 总结

Web Components 不是为了取代 React/Vue,而是为了补充组件互操作性的缺失。在构建设计系统(Design System)或跨框架微前端应用时,它是一个极具前瞻性的选择。

相关推荐
糖墨夕6 小时前
超越随机:JavaScript中真正可靠的唯一标识符生成策略
前端·javascript
码界奇点6 小时前
基于SpringBoot3+Vue的前后端分离电商系统设计与实现
前端·javascript·vue.js·spring·毕业设计·鸿蒙系统·源代码管理
wordbaby6 小时前
macOS ⇄ Android 局域网无线传输 APK 终极方案
前端
m0_471199636 小时前
【vue】通俗易懂的剖析vue3的响应式原理
前端·javascript·vue.js
LYFlied6 小时前
【一句话概括】前端项目包管理器怎么选?
前端·npm·pnpm·yarn
Sui_Network6 小时前
Sui 主网升级至 V1.61.2
大数据·前端·人工智能·深度学习·区块链
哟哟耶耶6 小时前
css-Echarts图表tooltip / label文本过长 超出屏幕边缘或容器范围
前端·javascript·echarts
郑州光合科技余经理6 小时前
解决方案:全球化时代下的海外版外卖系统
大数据·开发语言·前端·javascript·人工智能·架构·php
qq_172805596 小时前
Modbus数据采集 Web 平台介绍
前端