Web Components 与 React/Vue/Angular 框架集成实战指南

By | 2026年7月8日

引言

Web Components 是一组原生浏览器 API,允许开发者创建可复用的自定义元素。然而,在实际项目中,我们往往需要在 React、Vue 或 Angular 等框架中使用这些组件。由于框架的虚拟 DOM 和事件系统与原生元素存在差异,直接使用可能会遇到数据绑定失效、事件丢失等问题。本文将手把手教你如何正确集成 Web Components 与三大框架,并分享最佳实践。

1. 创建一个简单的 Web Component

首先,我们创建一个名为 的 Web Component,用于展示用户信息。


// user-card.js
class UserCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._name = '';
    this._age = 0;
  }

  static get observedAttributes() {
    return ['name', 'age'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'name') this._name = newValue;
    if (name === 'age') this._age = newValue;
    this.render();
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = `
      <style>
        .card { border: 1px solid #ccc; padding: 10px; border-radius: 8px; }
        .name { font-weight: bold; }
        .age { color: #666; }
      </style>
      <div class="card">
        <div class="name">${this._name}</div>
        <div class="age">${this._age} years old</div>
        <button id="greet">Greet</button>
      </div>
    `;
    this.shadowRoot.querySelector('#greet').addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('greet', { detail: this._name }));
    });
  }
}

customElements.define('user-card', UserCard);

该组件接收 nameage 属性,并触发 greet 自定义事件。

2. 在 React 中使用 Web Components

React 对 Web Components 的支持较为友好,但需要注意属性传递和事件处理。

2.1 安装与引入


npm install --save user-card
# 或直接引入脚本

在 React 组件中引入:


// 在入口文件或组件中引入
import 'user-card';

2.2 使用组件


import React, { useRef, useEffect } from 'react';

function App() {
  const cardRef = useRef(null);

  useEffect(() => {
    const card = cardRef.current;
    const handleGreet = (e) => {
      alert(`Hello, ${e.detail}!`);
    };
    card.addEventListener('greet', handleGreet);
    return () => card.removeEventListener('greet', handleGreet);
  }, []);

  return (
    <div>
      {/* 注意:React 中属性名需使用驼峰,但 Web Components 属性名是 kebab-case */}
      <user-card ref={cardRef} name="Alice" age={25} />
    </div>
  );
}

export default App;

注意

  • 属性 nameage 直接作为 HTML 属性传递,React 会自动处理字符串和数字。
  • 事件监听必须使用原生 addEventListener,因为 React 的合成事件无法直接捕获 Web Component 的自定义事件。
  • 如果属性是对象或数组,需要通过 JSON.stringify 转为字符串,或在 attributeChangedCallback 中解析。

2.3 传递复杂数据


<user-card user={JSON.stringify({name: 'Bob', age: 30})} />

在 Web Component 中解析:


attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'user') {
    const user = JSON.parse(newValue);
    this._name = user.name;
    this._age = user.age;
  }
  this.render();
}

3. 在 Vue 中使用 Web Components

Vue 3 对 Web Components 有很好的支持,但需要配置 isCustomElement 或使用 vue-web-component-wrapper(不推荐)。

3.1 配置 Vue 忽略自定义元素

vite.config.js 中:


import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith('user-'),
        },
      },
    }),
  ],
});

3.2 使用组件


<template>
  <div>
    <user-card :name="userName" :age="userAge" @greet="handleGreet" ref="cardRef" />
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import 'user-card';

const userName = ref('Alice');
const userAge = ref(25);
const cardRef = ref(null);

const handleGreet = (e) => {
  alert(`Hello, ${e.detail}!`);
};

// 注意:Vue 3 的 @greet 可以直接绑定自定义事件,但需要组件触发 CustomEvent
// 如果事件名是 kebab-case,Vue 会自动转换
</script>

注意

  • Vue 3 支持在模板中直接使用 @greet 监听自定义事件,但事件名必须全部小写(因为 HTML 属性不区分大小写)。
  • 属性绑定使用 :prop 语法,Vue 会自动将 camelCase 转为 kebab-case(例如 :userName 变为 user-name 属性)。

3.3 传递对象属性


<template>
  <user-card :user="userObject" />
</template>

<script setup>
const userObject = { name: 'Bob', age: 30 };
</script>

Vue 会将对象转为 JSON 字符串传递给属性,Web Component 需解析。

4. 在 Angular 中使用 Web Components

Angular 使用 Zone.js 和自定义事件处理,需要配置 CUSTOMELEMENTSSCHEMA。

4.1 配置模块


// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // 允许自定义元素
  bootstrap: [AppComponent],
})
export class AppModule {}

4.2 使用组件


// app.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <user-card
      [attr.name]="userName"
      [attr.age]="userAge"
      #cardRef
    ></user-card>
  `,
})
export class AppComponent implements AfterViewInit, OnDestroy {
  userName = 'Alice';
  userAge = 25;
  @ViewChild('cardRef', { static: true }) cardRef!: ElementRef;
  private listener!: (e: Event) => void;

  ngAfterViewInit() {
    this.listener = (e: Event) => {
      const customEvent = e as CustomEvent;
      alert(`Hello, ${customEvent.detail}!`);
    };
    this.cardRef.nativeElement.addEventListener('greet', this.listener);
  }

  ngOnDestroy() {
    this.cardRef.nativeElement.removeEventListener('greet', this.listener);
  }
}

注意

  • 属性绑定必须使用 [attr.name] 语法,否则 Angular 会尝试进行双向绑定。
  • 事件监听必须通过原生 API,因为 Angular 的 (greet) 语法无法直接识别自定义事件(除非使用 @Output 装饰器包装,但 Web Component 不是 Angular 组件)。
  • 如果属性是对象,可以使用 [attr.user]="userObject | json" 但需注意 JSON 序列化。

5. 最佳实践与常见坑

5.1 属性命名

Web Components 使用 kebab-case 属性名,而 React 和 Vue 推荐 camelCase。在框架中传递时,框架会自动转换,但需注意:

  • React:直接写 kebab-case 属性名,如 user-name
  • Vue:使用 :userName 会转换为 user-name
  • Angular:使用 [attr.user-name]

5.2 事件处理

  • 所有框架中,自定义事件都建议使用原生 addEventListener 监听,因为框架的合成事件系统可能无法正确处理。
  • Vue 3 的 @ 语法可以工作,但事件名必须全部小写。
  • Angular 可以通过 @HostListeneraddEventListener

5.3 生命周期

Web Components 的 connectedCallback 对应框架的挂载阶段,disconnectedCallback 对应卸载。确保在卸载时移除事件监听,避免内存泄漏。

5.4 样式隔离

Web Components 的 Shadow DOM 提供样式隔离,但框架的全局样式无法穿透。如果需要覆盖样式,可以使用 CSS 自定义属性(CSS Variables)或 ::part 伪元素。


user-card {
  --card-border-color: blue;
}

在 Web Component 中使用:


.card {
  border-color: var(--card-border-color, #ccc);
}

总结

本文通过一个完整的 user-card 示例,演示了 Web Components 与 React、Vue 和 Angular 的集成方法。核心要点包括:

  • 属性传递:使用原生属性,复杂数据需序列化。
  • 事件监听:优先使用原生 addEventListener
  • 生命周期管理:注意在卸载时清理。
  • 样式隔离:利用 CSS 自定义属性进行主题定制。

Web Components 与框架的集成并不复杂,但需要理解两者之间的差异。掌握这些技巧后,你可以在项目中无缝使用任何 Web Component,享受跨框架复用的便利。

延伸阅读