Web ComponentsはすでにW3C標準となっており、ChromeとFirefoxでネイティブサポートされています。フレームワークに依存せず、ネイティブブラウザでコンポーネント化を実現できます。
4つのコアAPI
- Custom Elements:カスタムHTML要素の定義
- Shadow DOM:スタイルとDOMの分離
- HTML Templates:再利用可能なHTMLテンプレート
- ES Modules:モジュール化(すでに標準)
カスタム要素の定義
javascript
// my-button.js
class MyButton extends HTMLElement {
// 監視する属性(変化時にattributeChangedCallbackが呼ばれる)
static get observedAttributes() {
return ["type", "disabled", "loading"];
}
constructor() {
super();
// Shadow DOMを作成
this.attachShadow({ mode: "open" });
// 初期化
this.render();
}
connectedCallback() {
// 要素がDOMに挿入されたときに呼ばれる
this.shadowRoot
.querySelector("button")
.addEventListener("click", this._handleClick);
}
disconnectedCallback() {
// 要素がDOMから削除されたときに呼ばれる(イベントのクリーンアップ)
this.shadowRoot
.querySelector("button")
.removeEventListener("click", this._handleClick);
}
attributeChangedCallback(name, oldValue, newValue) {
// 属性変化時に再レンダリング
this.render();
}
_handleClick = (e) => {
if (this.hasAttribute("disabled")) {
e.preventDefault();
return;
}
// カスタムイベントをディスパッチ
this.dispatchEvent(new CustomEvent("my-click", { bubbles: true }));
};
render() {
const type = this.getAttribute("type") || "default";
const disabled = this.hasAttribute("disabled");
const loading = this.hasAttribute("loading");
this.shadowRoot.innerHTML = `
<style>
:host { display: inline-block; }
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
background: ${type === "primary" ? "#409eff" : "#fff"};
color: ${type === "primary" ? "#fff" : "#333"};
}
</style>
<button ${disabled ? "disabled" : ""}>
${loading ? "読み込み中..." : "<slot></slot>"}
</button>
`;
}
}
customElements.define("my-button", MyButton);
Web Componentsは現在ブラウザサポートが充実しており、フレームワーク間で動作するUIライブラリを構築する際の確かな選択肢です。