JS 實戰 (4) —— Web Components:打造原生響應式 UI 元件
雖然現代開發大多擁抱 Vue 或 React,但 Web 標準從未停下腳步。Web Components 是一套由瀏覽器原生支援的組件化技術。它不依賴任何第三方庫,卻能實現高度封裝的 UI。
今天,我們將結合前幾篇學到的 Accessor (Get/Set),來解決 Web Components 最核心的挑戰:數據響應與屬性反射 (Property-Attribute Reflection)。
一、 Web Components 的三大支柱
在動手之前,我們必須認識這三位老朋友:
- Custom Elements (自定義元素):讓你可以定義像
<my-card>這樣的標籤。 - Shadow DOM (影子 DOM):實現真正的 CSS 與 DOM 封裝,內部樣式不會洩漏到外部。
- HTML Templates:定義不會在加載時立即渲染的結構。
二、 核心挑戰:Attribute (特徵) vs Property (屬性)
在 HTML 中,<input value="123"> 裡的 value 是 Attribute(字串)。在 JS 中,input.value 則是 Property(可能是任意型別)。
在原生開發中,這兩者並不會自動同步。這就是 Getter/Setter 大顯身手的地方。
三、 實戰演練:一個響應式的「進度條」組件
我們希望打造一個 <my-progress> 組件,當我們在 JS 中修改它的百分比時,畫面會自動更新。
javascript
class MyProgress extends HTMLElement {
constructor() {
super();
// 1. 開啟影子 DOM
this.attachShadow({ mode: "open" });
this._percent = 0; // 內部的倉庫 (Backing Field)
this._render();
}
// 2. 核心:實現數據反射 (Reflection)
get percent() {
return this._percent;
}
set percent(val) {
// 數據驗證
const constrainedVal = Math.min(100, Math.max(0, Number(val)));
if (this._percent === constrainedVal) return;
this._percent = constrainedVal;
// 同步到 Attribute (這樣 CSS 也可以讀到)
this.setAttribute("percent", constrainedVal);
// 更新畫面
this._updateUI();
}
// 3. 監聽 Attribute 變化 (實現反向反射)
static get observedAttributes() {
return ["percent"];
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === "percent" && oldVal !== newVal) {
this.percent = newVal; // 觸發 setter
}
}
_render() {
this.shadowRoot.innerHTML = `
<style>
.bar { width: 100%; height: 20px; background: #eee; border-radius: 10px; overflow: hidden; }
.inner { height: 100%; background: var(--ui-primary, #4ade80); transition: width 0.3s; }
</style>
<div class="bar">
<div class="inner" style="width: ${this._percent}%"></div>
</div>
`;
}
_updateUI() {
const inner = this.shadowRoot.querySelector(".inner");
if (inner) inner.style.width = `${this._percent}%`;
}
}
customElements.define("my-progress", MyProgress);四、 為什麼要用 Accessors 處理 Web Components?
- 類型安全:Attribute 永遠是字串,透過 Setter 你可以將其轉換為 Number、Boolean。
- 邏輯觸發:修改屬性的同時,可以觸發 UI 重新渲染或發送自定義事件 (CustomEvent)。
- 封裝性:外部使用者只需要像操作一般 DOM 一樣
el.percent = 50,不需要了解內部的 Shadow DOM 結構。
五、 實戰建議:何時該用 Web Components?
- 跨框架需求:如果你的組件要在 React 公司與 Vue 公司之間共用。
- 基礎 UI 庫:按鈕、輸入框、彈窗等高度通用的基礎原子。
- 微前端架構:作為不同子應用間的通訊媒介與沙箱環境。
️ 進階挑戰
- 實作挑戰:為上面的
MyProgress加入一個color屬性。當使用者修改顏色時,進度條的顏色應自動變化。 - 深度思考:為什麼 Web Components 的
attributeChangedCallback需要搭配一個靜態的observedAttributes清單?直接監聽所有的 Attribute 不好嗎?
延伸閱讀與資源
- Web.dev: Custom Elements on the Web
- MDN: Using Shadow DOM
- Lit: 由 Google 打造,極致簡約的 Web Components 庫