Closed7

Web Componentsの作り方

nissy-devnissy-dev

Web componentsとは

Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです

一連のテクノロジーが指すものとしては、以下の3つがある

  • Custom elements
  • Shadow DOM
  • HTML templates

https://developer.mozilla.org/ja/docs/Web/Web_Components

nissy-devnissy-dev

Custom elements について

とりあえず以下のMDNを読んで、手を動かして学んでみた
https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_custom_elements

大きく分けて2つの作成方法がある

  • 標準のHTML要素を継承する
    • HTMLParagraphElementやHTMLInputElementなどをextendsしたクラスを作る
  • 標準のHTML要素を継承しないで独自の要素を作成する
    • ただのHTMLElementをextendsしたクラスを作る

標準のHTML要素を拡張する場合は少し注意が必要で、定義する際にどのタグを拡張したか明示する必要があったり、HTMLで利用する際も is 属性を付与する必要がある

定義方法:customElements.define('word-count', WordCount, { extends: 'p' });)
タグの利用方法:<p is="word-count"> or document.createElement("p", { is: "word-count" })

標準のHTML要素を拡張すると reactのように <word-count></word-count> とはできない

nissy-devnissy-dev

Custom elementsの具体的なDOM要素は、shadow DOMを使って構築する。

Shadow DOMは、通常のDOMツリーのあるノードに対して、そのノードを根としたカプセル化されたDOM ツリーを追加することができる仕組み。実は長年使用されていたらしい。

shadow DOM は全く新しいものではなく、例えばブラウザにおいて要素の内部構造をカプセル化するために長年使用されていました。 <video> 要素の例を考えます。DOM で見えるものは <video> 要素のみですが、その shadow DOM の内部ではたくさんのボタンや他の制御コードが含まれています。shadow DOM スペックができたことにより、この機能を実際に操作しカスタム要素で shadow DOM を作ることができるようになりました。

DOMツリーの構築方法は、普通にcreateElementで要素を作って、それをshadowRoot (element.attachShadow({mode: 'open'})で作成できる) にappendする感じ。スタイルに関しては、styleタグ内で記述 or CSSファイルをlinkタグでの読み込みで対応する。

(詳しいコードはMDNを参考)

https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_shadow_DOM

nissy-devnissy-dev

Custom elements のライフサイクルコールバック

よく使いそうなのは、connectedCallbackattributeChangedCallback
属性を見て動的な操作をしたい場合、初回レンダリングだけならconnectedCallback、初回レンダリング以外も操作したいならattributeChangedCallbackを使うのが良さそう。

  • connectedCallback
    • Custom elementsがページに追加されたとき
  • disconnectedCallback
    • Custom elementsがページから削除されたとき
  • adoptedCallback
    • Custom elementsが別のページに移動されたとき
  • attributeChangedCallback
    • Custom elementsの属性の1つが追加、削除、または変更されたとき
    • 変更を通知する属性は、 static get observedAttributes() メソッドで指定
nissy-devnissy-dev

TemplateとSlot

Template要素

  • ページにはレンダリングされないが、JavaScript から参照することができるDOM要素
  • 表示したい場合は、追加したい要素にJSでappendする

Custome elementsは、基本的にJSでDOMを構築する必要があり、コードが直感的に理解しにくくなるという印象があったけど、templateを使うときれいにかけそう

Templateなし

<html>
<body>
  <sample-tag></sample-tag>

  <script>
    class SampleTag extends HTMLElement {
      constructor() {
        super();
        const shadow = this.attachShadow({mode: 'open'});
        const span1 = document.createElement('span');
        span.textContent = 'SampleTag1';
        const span2 = document.createElement('span');
        span.textContent = 'SampleTag2';
        shadow.appendChild(span1);
        shadow.appendChild(span2);
      }
    }
    customElements.define('sample-tag', SampleTag);
  </script>
</body>
</html>

Templateあり

<html>
<body>
  <template id="sample-tag">
    <span>SampleTag1</span>
    <span>SampleTag2</span>
  </template>

  <sample-tag></sample-tag>

  <script>
    class SampleTag extends HTMLElement {
      constructor() {
        super();
        const template = document.getElementById('my-paragraph');
        const templateContent = template.content;
        this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true));
      }
    }
    customElements.define('sample-tag', SampleTag);
  </script>
</body>
</html>

Slot要素

  • Template要素をもっと柔軟に扱うために作られたタグ
  • template内の要素うち、slotタグで囲まれた内容は置き換えが可能

これはコードを見たほうが早いので、以下に例を示す

<template>
  <p><slot name="my-name">デフォルトテキスト</slot></p>
</template>

/* デフォルトテキストをspanタグで置き換える */
<my-paragraph>
  <span slot="my-name">新しいテキストを代入します</span>
</my-paragraph>

https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_templates_and_slots

nissy-devnissy-dev

ラジオグループをWeb Componentsで作ってみた感想 (主にReactとの違い)

  • 属性には、文字列 or フラグしか渡せない
    • 当たり前だけど複雑な値を属性で受け取ったりするのはやめたほうが良い
  • コンポーネントをまたぐデータ制御は大変
    • 今回問題になったチェックされている値の排他制御は、JSで1から実装する必要がある

Gistで見つけたRadio Groupの実装
https://gist.github.com/robdodson/85deb2f821f9beb2ed1ce049f6a6ed47

チームで壁打ちしたときのコメント

  • Web Components は、どのライブラリ (React/Vue/Angular) でも使えるようなコンポーネントとしての立ち位置で使うのがよさそう
    • どの粒度のコンポーネントを共通化するか迷う
      • Atom/Moleculeレベルの粒度のパーツが適切に感じる
      • 複数の要素をまたぐロジックをJSで書くのがかなり大変なため
    • Vueは、作成したコンポーネントをWeb components化できたりする
  • GitHubが公開したWeb Componentsは参考になりそう
このスクラップは2021/10/27にクローズされました