React19の新機能のうち、これからの開発が約束されている事にWeb Componentsとのインテグレーションをシンプルにすると語られている事を最近しりました。参考ブログ:New Features in React 19 — Web Components
そいえばWeb Componentsってあったよね、でも知らないよね。ということでWeb Componentsを学んでみる事にしました。
Bing Image Creatorが生成した画像を使っています
Web Componentsとは
Reactはコンポーネントを作ることで事でアプリを構築しますが、Web ComponentsはWebの標準としてコンポーネント(カスタムエレメント)を作れる仕様で、既にブラウザーに実装されています。
Web ComponentsとReactの歴史
Web Componentsの歴史を調べてみると、Reactの歴史と重なる事が多いのに驚きました。
年 | Web Components | React |
---|---|---|
2011 | Alex RussellがFronteers Conferenceで発表 | FacebookのNews Feedアプリで使われる |
2013 | GoogleがPolymerを公開 | JSConfでオープンソースとして公開される |
2014 | Google Chromeがサポート開始 | |
2015 | 日本でも使われ出し、私もある仕事でReactを採用 | |
2016 | Safri がサポート開始 | |
2018 | Firefox がサポート開始 | |
2019 | 簡単に書ける Litが公開 | Hooksが入り関数コンポーネント中心になる |
Web Componentsの要素
Web Componentsは以下の要素から出来ています(Wikipedaからの引用)
- Custom Elements : 再利用可能なまとまり(コンポーネント)としてカスタムHTML要素を定義可能にする
- Shadow DOM : CSSの適用範囲・JavaScriptのアクセス範囲を限定し独立したコンポーネント化を可能にする
- HTML Templates : HTML要素群のテンプレート化を可能にし(template要素)インスタンスへの要素注入を可能にする(slot要素)
私は、Shadow DOMはReact等で使われているVirtual DOMと同じもかと思っていました。😅
サンプルコード
いつものジャンケンアプリを作ろうと思ったのですが、日本語・英語共にドキュメントやブログ記事が少なく、かつ完全にReact脳になっていいる私には半日ではつくれませんでした。下の画像のようなカウンターをが精一杯でした。
Web標準のみで作ったサンプル
- ① Web Componentsの実体(?)はHTMLElementを継承したJavaScriptのClassで作ります
- ② クラスの持つインスタンス変数にComponentsの持つ値を保持します
- ③
connectedCallback
はコンポーネントが画面(DOM)に追加された時に呼び出されるメソッドです- Web Componentsのクラスにはいくつかのコールバック・メソッドが定義されています
- ここでは、表示メソッドが呼び出しています
- ④ ボタンが押された時に呼び出されるcountUpメソッドです
- countインスタス変数を+1しています
- ⑤ Web Componentsはリアクティブ(宣言的UI)ではありませのでコードで画面を描画する必要があります
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Counter</title>
</head>
<body>
<script>
class CounterElement extends HTMLElement { // ← ①
constructor() {
super();
this.count = 1; // ← ②
}
connectedCallback() { // ← ③
this.render();
}
countUp() { // ← ④
this.count++; // ↓ ⑤
this.shadowRoot.querySelector("#count_view").textContent = String(this.count);
}
render() { // ← ⑥
this.attachShadow({ mode: 'open' }); // ← ⑦ // ↓ ⑧
this.shadowRoot.innerHTML = `
<p>Count: <span id="count_view">${this.count}</span></p>
<button id="count_button">CountUp</button>
`;
// ↓ ⑨
const countButton = this.shadowRoot.querySelector('#count_button');
countButton.addEventListener("click", () => this.countUp()); // ← ⑨
}
}
customElements.define("counter-element", CounterElement); // ← ⑩
</script>
<counter-element></counter-element> <!-- ← ⑪ -->
</body>
</html>
- ⑥ 表示用メソッドrenderです
- ⑦ attachShadowメソッドでShadow DOMをDOMに追加しています
mode: 'open'
なのでShadow DOM内の要素はJavaScriptからアクセスできます
- ⑧ Shadow DOMにHTMLを挿入しています
- この部分にHTML Templatesを使いたかったのですが。今回は出来ませんでした
- ⑨ ボタンにクリックイベントを設定しています
- ⑩ CounterElementsプロパティーに
counter-element
という名前でCounterElementを登録しています - ⑪ カスタムエレメント
<counter-element>
を利用しています
短いコードですが、ここまで作るのに時間がかかってしまいました。😅
Web Components用フレームワーク Lit
Litというフレームワークを使うと簡単にWeb Componentsが作れるようです。
Litのホームページによると(DeepL翻訳です)
- Custom Elements : Litコンポーネントは標準的なカスタム要素なので、ブラウザは組み込み要素とまったく同じように扱います。手書きのHTMLやフレームワークのコードで使用したり、CMSや静的サイト・ビルダーから出力したり、JavaScriptでインスタンスを作成することもできます!
- Scoped styles : Lit はデフォルトで、Shadow DOM を使用してスタイルをスコープします。これにより、CSSセレクタがシンプルに保たれ、コンポーネントのスタイルがページ上の他のスタイルに影響しない(影響されない)ことが保証されます。
- Reactive properties : リアクティブ・プロパティを宣言して、コンポーネントの API と内部状態をモデル化します。リアクティブプロパティ(または対応する HTML 属性)が変更されるたびに、Lit コンポーネントは効率的に再レンダリングします。
- Declarative templates : タグ付きテンプレート・リテラルをベースにしたLitテンプレートは、シンプルで表現力豊か、そして高速です。カスタム構文を覚える必要もなく、コンパイルも不要です。
サンプルコード
- ① ここではHTMLファイル内で直接Litライブラリーをインポートしています
- ② Litを使うにはLitElementを継承します
- ③ リアクティブな変数を定義しています(Reactのステートのようですね)
- ④ カウントアップ処理内では、count変数を+1するだけで表示は自動的に行われます
- ⑤ 表示用のrenderメソッドです
- htmlタグ付きテンプレートで効率的にHTMLを作成します
@click=
はイベントハンドラーの設定ですね
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Counter</title>
</head>
<body>
<script type="module">
import { LitElement, html } from "https://esm.sh/lit"; // ← ①
class CounterElement extends LitElement { // ← ②
static properties = { // ← ③
count: {type: Number}
};
constructor() {
super();
this.count = 1;
}
countUp() { // ← ④
this.count++;
}
render() { // ← ⑤
return html`
<p>Count: <span id="count_view">${this.count}</span></p>
<button id="count_button" @click=${this.countUp}>CountUp</button>
`;
}
}
customElements.define("counter-element", CounterElement);
</script>
<counter-element></counter-element>
</body>
</html>
Reactive propertiesがあるのでReactのように書けますね! Litを使えば簡単にWeb Componentsが作れそうですね。
まとめ
ReactやVue等は、効率的でメンテナンス性の高いアプリケーションを作るために作られたものです。対してWeb Componentsはwebにカスタムエレメントを追加するために作られた標準仕様なので、そのままでは使いやすさ等は今一つでした。
そこにLit のような使いやすいフレームワークが現れてきたのは必然だと思います。しかし今のところReactから乗り換えたいと思う点は無いです。
ただし、HTML Templatesがまた使えてないので、次回はいつものジャンケンアプリを作ってみたいと思います。