Web Components标准与自定义元素实战2026

本文详解Web Components标准,实现跨框架(React/Vue/Angular)组件复用。

Web Components核心标准

标准 作用 兼容性
Custom Elements 定义自定义HTML标签 Chrome 54+, Firefox 63+
Shadow DOM 样式/DOM隔离 Chrome 53+, Firefox 63+
HTML Templates 声明式模板 全部现代浏览器
ES Modules 模块导入 Chrome 61+, Firefox 60+

创建第一个Web Component

定义自定义元素

// my-button.js
class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        button {
          background: ${this.getAttribute('color') || 'blue'};
          color: white;
          border: none;
          padding: 8px 16px;
          border-radius: 4px;
          cursor: pointer;
        }
      </style>
      <button>
        <slot></slot>
      </button>
    `;

    this.shadowRoot.querySelector('button')
      .addEventListener('click', () => {
        this.dispatchEvent(new CustomEvent('my-button-click', {
          bubbles: true,
          composed: true,  // 可以穿过Shadow DOM边界
        }));
      });
  }
}

customElements.define('my-button', MyButton);

使用自定义元素

<!DOCTYPE html>
<html>
<head>
  <script src="my-button.js"></script>
</head>
<body>
  <my-button color="green">点击我</my-button>

  <script>
    document.querySelector('my-button')
      .addEventListener('my-button-click', () => {
        alert('按钮被点击!');
      });
  </script>
</body>
</html>

在React中使用Web Components

React 18+ 原生支持

import './my-button.js';  // 导入Web Component

function App() {
  return (
    <div>
      {/* 直接使用自定义元素 */}
      <my-button color="red" onClick={() => console.log('React clicked')}>
        React中的Web Component
      </my-button>
    </div>
  );
}

处理事件(React 18- 需要特殊处理)

useEffect(() => {
  const handleClick = (e) => {
    console.log('Web Component clicked', e.detail);
  };

  document.querySelector('my-button')
    .addEventListener('my-button-click', handleClick);

  return () => {
    document.querySelector('my-button')
      .removeEventListener('my-button-click', handleClick);
  };
}, []);

在Vue 3中使用Web Components

配置Vue忽略自定义元素

// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => {
        options.compilerOptions.isCustomElement = tag => tag.includes('-');
        return options;
      });
  },
};

在Vue模板中使用

<template>
  <div>
    <my-button color="purple" @my-button-click="handleClick">
      Vue中的Web Component
    </my-button>
  </div>
</template>

<script setup>
function handleClick() {
  console.log('Vue received click');
}
</script>

在Angular中使用Web Components

配置CUSTOM_ELEMENTS_SCHEMA

// 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 {}

在Angular组件中使用

// app.component.ts
import { Component, AfterViewInit } from '@angular/core';
import './my-button.js';  // 导入Web Component

@Component({
  selector: 'app-root',
  template: `
    <my-button (my-button-click)="handleClick()">
      Angular中的Web Component
    </my-button>
  `,
})
export class AppComponent implements AfterViewInit {
  ngAfterViewInit() {
    const button = document.querySelector('my-button');
    button.addEventListener('my-button-click', () => {
      console.log('Angular received click');
    });
  }

  handleClick() {
    console.log('Angular handler');
  }
}

高级:响应式属性

使用observedAttributes

class MyCounter extends HTMLElement {
  static get observedAttributes() {
    return ['count'];  // 监听这些属性的变化
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'count') {
      this.updateUI(parseInt(newValue));
    }
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.count = 0;
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <div>
        <button id="dec">-</button>
        <span id="count">0</span>
        <button id="inc">+</button>
      </div>
    `;

    this.shadowRoot.getElementById('inc')
      .addEventListener('click', () => {
        this.count++;
        this.setAttribute('count', this.count);
      });

    this.shadowRoot.getElementById('dec')
      .addEventListener('click', () => {
        this.count--;
        this.setAttribute('count', this.count);
      });
  }

  updateUI(count) {
    this.shadowRoot.getElementById('count').textContent = count;
  }
}

customElements.define('my-counter', MyCounter);

使用

<my-counter count="5"></my-counter>

<script>
  const counter = document.querySelector('my-counter');

  // 改变属性会自动触发 attributeChangedCallback
  counter.setAttribute('count', 10);
</script>

在WordPress中集成Web Components

注册自定义块(Gutenberg)

// 注册Web Component块
add_action('init', function() {
  wp_register_script(
    'my-web-component-block',
    get_template_directory_uri() . '/blocks/my-counter.js',
    array('wp-blocks', 'wp-element')
  );

  register_block_type('my-theme/my-counter', array(
    'editor_script' => 'my-web-component-block',
  ));
});
// blocks/my-counter.js (Gutenberg块)
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';

// 导入Web Component
import './my-counter.js';

registerBlockType('my-theme/my-counter', {
  title: 'My Counter',
  edit({ attributes, setAttributes }) {
    return (
      <div {...useBlockProps()}>
        <my-counter count={attributes.count}></my-counter>
      </div>
    );
  },
  save({ attributes }) {
    return <my-counter count={attributes.count}></my-counter>;
  },
});

2026年Web Components趋势

趋势一:声明式Shadow DOM

<!-- 无需JavaScript即可创建Shadow DOM -->
<custom-element>
  <template shadowroot="open">
    <style>
      :host { background: lightblue; }
    </style>
    <slot></slot>
  </template>
  <p>Light DOM内容</p>
</custom-element>

趋势二:Web Components + SSR

// 服务器端渲染Web Components
import { render } from '@lit-labs/ssr';

const html = render(html`<my-button>Hello</my-button>`;
// 输出:<my-button><template shadowroot="open">...</template>Hello</my-button>

趋势三:Web Components作为微前端通信桥梁

// 各框架通过Custom Events通信
document.addEventListener('cart-update', (e) => {
  // React/Vue/Angular都能接收
  updateCart(e.detail.items);
});

性能对比

方案 首屏加载 交互响应 内存占用
React组件 快(CSR) 高(Virtual DOM)
Vue组件 快(CSR)
Web Components 最快(原生) 最快 最低

决策建议

  • 跨框架组件库 → Web Components(一次编写,到处使用)
  • 单一框架项目 → 框架原生组件(React/Vue)
  • 微前端架构 → Web Components(各子应用通信)
  • 高性能要求 → Web Components(无框架开销)

Web Components是2026年跨框架组件复用的标准方案,建议在组件库中使用。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。