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)或跨框架微前端应用时,它是一个极具前瞻性的选择。

相关推荐
2分钟速写快排13 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60611 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen4 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒5 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端
乘风gg6 小时前
DeepSeek V4.1 Flash 来了,明天中午 Flash 降价 60%
前端·ai编程·claude