ES2022でクラス周りの構文が強化されたため、Custom Elementが書きやすくなっています。
class private field
クラスにprivateフィールドを作成できる機能です。
この機能は、Custom Element内でShadow DOMを使う時に効果を発揮します。
Shadow DOMはelement.attachShadow
という関数を使用して作成するのですが、生成したshadow DOMへの参照を隠蔽するのは従来は困難でした。
class FooElement extends HTMLElement {
connectedCallback() {
// shadow DOMを隠蔽するには `mode: "closed"` にする
this._shadow = this.attachShadow({ mode: "closed" });
this._shadow.appendChild(document.createElement("div"));
}
}
// `mode: "closed"`にしたにもかかわらず、shadow domにアクセスできてしまう
new FooElement()._shadow
アンダースコア始まりの疑似privateを使っている場合、クラスの外部からもアクセスできてしまうため、shadow DOMへの参照は隠蔽できません。
もし完全にshadow DOMへの参照を隠蔽したい場合は、全体を即時関数で囲ったうえでWeakMapに参照を保存する等の努力が必要でした。そのため、closed modeでshadow DOMを作成する価値はないという意見もありました(参考:こちらのブログ記事(英語))。
しかし、ES2022で導入されたprivateフィールドを使うと、shadow DOMへの参照を外部から隠蔽することができます。
class FooElement extends HTMLElement {
#shadow // private fieldを使ってshadow DOMへの参照を保持
connectedCallback() {
this.#shadow = this.attachShadow({ mode: "closed" });
this.#shadow.appendChild(document.createElement("div"));
}
}
new FooElement().#shadow // エラー(shadow domにアクセスできない)
このように、privateフィールドとshadow DOMを組み合わせることで、custom elementのより強いカプセル化が可能になりました。
class static block
custom elementを作成するには、作成したcustom element用クラスをcustomElement.define
関数で登録する必要があります。
// custom element用クラスの定義
class FooElement extends HTMLElement {
// 省略
}
// custom element用クラスを登録
customElements.define("foo-element", FooElement);
これまでは上のように、customElements.define
はクラスの外側に書くしかありませんでしたが、ES2022で導入されたclass static blockを使うとクラスの内側に書くことができます。
// custom element用クラスの定義
class FooElement extends HTMLElement {
static {
customElements.define("foo-element", this);
}
// 省略
}
static {}
という部分の中身が、クラスの初期化時に実行されるため、customElements.define
の実行に使えるというわけです。
まとめ
- Custom Elementには普通のプロパティやメソッドだけでなく、privateなプロパティやメソッドも設定できる
- privateプロパティにshadow DOMへの参照を保存すると、外部からshadow DOMへのアクセスを遮断できる(※)
- class static block内で
customElements.define
を実行できる
(※)ちなみに、private propertyにすれば完全にshadow DOMへのアクセスを防げるというわけではなく、以下のようなコードを挿入されると外部からshadow DOMへアクセスすることが可能になってしまいます。
const originalAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function() {
return originalAttachShadow.call(this, { mode: "open" });
};
なので、完全なprivate化ができるわけではないという事に注意してください。