Web Componentsの作り方
Web componentsとは
Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです
一連のテクノロジーが指すものとしては、以下の3つがある
- Custom elements
- Shadow DOM
- HTML templates
Custom elements について
とりあえず以下のMDNを読んで、手を動かして学んでみた
大きく分けて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>
とはできない
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を参考)
Custom elements のライフサイクルコールバック
よく使いそうなのは、connectedCallback
やattributeChangedCallback
属性を見て動的な操作をしたい場合、初回レンダリングだけならconnectedCallback
、初回レンダリング以外も操作したいならattributeChangedCallback
を使うのが良さそう。
- connectedCallback
- Custom elementsがページに追加されたとき
- disconnectedCallback
- Custom elementsがページから削除されたとき
- adoptedCallback
- Custom elementsが別のページに移動されたとき
- attributeChangedCallback
- Custom elementsの属性の1つが追加、削除、または変更されたとき
- 変更を通知する属性は、
static get observedAttributes()
メソッドで指定
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>
MDNのサンプルコードはかなり参考になる
ラジオグループをWeb Componentsで作ってみた感想 (主にReactとの違い)
- 属性には、文字列 or フラグしか渡せない
- 当たり前だけど複雑な値を属性で受け取ったりするのはやめたほうが良い
- コンポーネントをまたぐデータ制御は大変
- 今回問題になったチェックされている値の排他制御は、JSで1から実装する必要がある
Gistで見つけたRadio Groupの実装
チームで壁打ちしたときのコメント
- Web Components は、どのライブラリ (React/Vue/Angular) でも使えるようなコンポーネントとしての立ち位置で使うのがよさそう
- どの粒度のコンポーネントを共通化するか迷う
- Atom/Moleculeレベルの粒度のパーツが適切に感じる
- 複数の要素をまたぐロジックをJSで書くのがかなり大変なため
- Vueは、作成したコンポーネントをWeb components化できたりする
- どの粒度のコンポーネントを共通化するか迷う
- GitHubが公開したWeb Componentsは参考になりそう