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年跨框架组件复用的标准方案,建议在组件库中使用。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

评论(0)