Web Components 原生元件開發完全指南
Web Components 原生元件開發完全指南
在現代前端開發中,React、Vue、Angular 等框架幾乎佔據了主導地位。然而,有一種被許多人忽視的技術——Web Components——它是瀏覽器原生支援的元件化解決方案,不依賴任何框架,跨框架可用,且擁有極佳的長期穩定性。
什麼是 Web Components?
Web Components 是一組瀏覽器 API 的集合,主要由三個核心技術組成:
- Custom Elements:允許定義自訂 HTML 元素
- Shadow DOM:提供封裝的 DOM 和樣式
- HTML Templates:可重複使用的 HTML 片段
Custom Elements 基礎
建立一個自訂元素非常直觀:
class MyButton extends HTMLElement {
constructor() {
super();
this._count = 0;
}
connectedCallback() {
this.render();
this.querySelector('button').addEventListener('click', () => {
this._count++;
this.render();
});
}
render() {
this.innerHTML = `
<button style="padding: 8px 16px; cursor: pointer;">
點擊次數:${this._count}
</button>
`;
}
}
customElements.define('my-button', MyButton);生命週期回呼
Custom Elements 提供幾個重要的生命週期方法:
class MyComponent extends HTMLElement {
connectedCallback() {
console.log('元素已連接到 DOM');
}
disconnectedCallback() {
console.log('元素已從 DOM 移除');
}
adoptedCallback() {
console.log('元素已移至新文件');
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(`屬性 ${name} 從 ${oldValue} 變更為 ${newValue}`);
}
static get observedAttributes() {
return ['color', 'size', 'disabled'];
}
}Shadow DOM 封裝
Shadow DOM 是 Web Components 最強大的特性之一,它能讓元件的樣式完全隔離:
class StyledCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
:host { display: block; font-family: sans-serif; }
.card {
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-title { font-size: 1.25rem; font-weight: bold; margin-bottom: 8px; }
`;
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="card-title"><slot name="title">預設標題</slot></div>
<div class="card-body"><slot>預設內容</slot></div>
`;
shadow.appendChild(style);
shadow.appendChild(card);
}
}
customElements.define('styled-card', StyledCard);使用方式:
<styled-card>
<span slot="title">我的卡片標題</span>
<p>這是卡片的主要內容。</p>
</styled-card>HTML Templates
使用 <template> 標籤可以定義不會立即渲染的 HTML 片段:
<template id="user-card-template">
<style>
.user-card { display: flex; align-items: center; gap: 12px; }
.avatar { width: 48px; height: 48px; border-radius: 50%; }
.info .name { font-weight: bold; }
.info .email { color: #666; font-size: 0.875rem; }
</style>
<div class="user-card">
<img class="avatar" src="" alt="Avatar">
<div class="info">
<div class="name"></div>
<div class="email"></div>
</div>
</div>
</template>class UserCard extends HTMLElement {
connectedCallback() {
const template = document.getElementById('user-card-template');
const clone = template.content.cloneNode(true);
clone.querySelector('.avatar').src = this.getAttribute('avatar') || '';
clone.querySelector('.name').textContent = this.getAttribute('name') || '';
clone.querySelector('.email').textContent = this.getAttribute('email') || '';
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(clone);
}
}
customElements.define('user-card', UserCard);與 Vue 3 整合
// main.js
import { createApp } from 'vue';
const app = createApp(App);
app.config.compilerOptions.isCustomElement = (tag) => tag.includes('-');
app.mount('#app');實際應用:通知元件
class NotificationToast extends HTMLElement {
static get observedAttributes() {
return ['type', 'message', 'duration'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
const duration = parseInt(this.getAttribute('duration') || '3000');
if (duration > 0) setTimeout(() => this.remove(), duration);
}
render() {
const message = this.getAttribute('message') || '';
this.shadowRoot.innerHTML = `
<style>
.toast {
padding: 12px 16px;
border-left: 4px solid #63b3ed;
background: #ebf8ff;
color: #2c5282;
border-radius: 4px;
font-family: sans-serif;
font-size: 14px;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>
<div class="toast">${message}</div>
`;
}
}
customElements.define('notification-toast', NotificationToast);瀏覽器支援
目前主流瀏覽器(Chrome、Firefox、Safari、Edge)均完整支援 Web Components 三大核心 API。
何時使用 Web Components?
適合使用的場景:
- 跨框架的設計系統(如 Adobe Spectrum、Shoelace)
- 嵌入第三方頁面的元件
- 需要長期維護且不想被框架版本綁定的元件庫
- 微前端架構中的共享元件
總結
Web Components 提供了一種原生、標準化的元件化方案。掌握它能讓你對瀏覽器原生能力有更深入的理解,在跨框架共用元件的場景中,它是無可替代的解決方案。
分享這篇文章