EY-Office ブログ

今更ながらWeb Componentsを学んでみた(1)

React19の新機能のうち、これからの開発が約束されている事にWeb Componentsとのインテグレーションをシンプルにすると語られている事を最近しりました。参考ブログ:New Features in React 19 — Web Components

そいえばWeb Componentsってあったよね、でも知らないよね。ということでWeb Componentsを学んでみる事にしました。

Web Components Bing Image Creatorが生成した画像を使っています

Web Componentsとは

Reactはコンポーネントを作ることで事でアプリを構築しますが、Web ComponentsはWebの標準としてコンポーネント(カスタムエレメント)を作れる仕様で、既にブラウザーに実装されています。

Web ComponentsとReactの歴史

Web Componentsの歴史を調べてみると、Reactの歴史と重なる事が多いのに驚きました。

Web ComponentsReact
2011Alex RussellがFronteers Conferenceで発表FacebookのNews Feedアプリで使われる
2013GoogleがPolymerを公開JSConfでオープンソースとして公開される
2014Google Chromeがサポート開始
2015日本でも使われ出し、私もある仕事でReactを採用
2016Safri がサポート開始
2018Firefox がサポート開始
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がまた使えてないので、次回はいつものジャンケンアプリを作ってみたいと思います。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ