この記事では、
- ReactやVueではなくWebComponentsだけを使いたい気持ちを胸に、とある社内プロジェクトをやってみての学び
- 巷にあふれるWebComponentsに対する見方への違和感
についてメモっておきます。
ただ「WebComponents」の語がもつ意味をきっちり定義してるわけではないので、そのへんは雰囲気で察してください。
そのせいで勘違いされがちな概念なんかも、知らんけど。
まずは巷にあふれる意見に対する気持ちから。
(React|Vue|Xxx)はもう古い、これからはWebComponentsだ!
勝手なイメージですが、こういう認識の人いるよね・・?
個人的には、「いや、WebComponentsはそういう技術じゃない」と思った・思ってます。
以下、ReactもVueもAngularも「ウェブアプリケーションを作るための技術」であるが、WebComponentsはそうじゃないという主張が続きます。
これはどっちかを選ぶようなもものでもない。
そもそもReactのドキュメントにも、共存する概念であるって書いてある。
なんで?いい感じにコンポーネント作れるんでしょ?
Yes, しかしNo。
その「コンポーネント」が、ウェブアプリケーションを構成する要素という意味ならNo。
あくまでHTMLの一要素としてWebを構成する部品という意味ならYes。
これは実際にコード書いてみるとすぐわかる。
const Btn = ({ onClick, children }) => ( <button type="button" onClick={ev => onClick(ev)} >{children}</button> );
このさもありなReactのコンポーネントらしきものを、WebComponentsで書き直そうとしてみる。
class Btn extends HTMLElement { connectedCallback() { this.attachShadow({ mode: 'open' }); this._render(); } _render() { this.shadowRoot.innerHTML = ` <button type="button"> <slot></slot> </button> `; } } // どこかで customElements.define('my-button', Btn);
・・と、途中で手が止まるはず。
WebComponentsとして、あくまでHTMLの一要素として定義した以上、`onClick`みたいなものは渡せない。
渡せるのはHTMLの属性値(つまりは文字列のみ)になる。
じゃあどうやってイベントを捕捉するのかというと、こうなる。
// またどこかで const btn = document.querySelector('my-button'); // からの btn.addEventListener('click', ev => onClick(ev), false);
カスタムイベント発行でもなんでもいいけど、基本的にこの懐かしい`addEventListener()`を使わないといけないってのがポイント。
Props的なものを渡したくても、全て文字列になってしまうってのがポイント。
というわけで、この時点でアプリケーションを構成する「コンポーネント」としては、まったく使い物にならないことがわかるはず。
2018年にもなって、jQuery w/ Backbone的なコードに帰れるなら話は別やが、俺には無理です。
なので、WebComponents"だけ"で、ウェブアプリケーションを作るなんてことを、そもそも考えてはいけない。
じゃあWebComponentsの存在意義って?
個人的には、「ウェブアプリケーションのドメインやら構成要素と切り離された、色味のない汎用的な部品を作るための技術」かと。
もちろん属性は渡せるけど、そこで大きく挙動を変えたりはしない謙虚な部品。
そんな部品を、
- いろんな場所で再利用する前提
- 何にも依存せず
作りたい場合には、選ばれる技術なのかなーと。
そういう点からすると、ここで書かれてる決済ボタンとかは良い例よねーと思った。
副産物とは思うけど、機能をJSから拡張して使えるようにする発想は賢いなーとも思った。
ただこの場合、決済機能が使えるJS-SDKがあれば全て事足りる気もしてるのも確か。
まぁポリシー的にこういう見た目じゃないと許しません!ってのがあるなら、WebComponentsで配布されるのは嬉しいんかも。
なので実際のところ、WebComponentsで本当に良かった!っていうコンポーネントには、早々お目にかかれないんやろうなとも思ってる。
lit-htmlとかhyperHTMLとかは?
ちょっと横道へ。
同じ文脈で登場しがちではあるけど、`lit-html`も`hyperHTML`も、WebComponentsとは直接関係ない。
HTML文字列を実際のDOMに変換してくれたり、画面に実際に描画したりするだけ。ReactでいうところのJSXと`ReactDOM.render()`の層。
よって、WebComponentsでコンポーネントを書く時に楽はできるけど、そもそもコンセプトが違うし、 = WebComponentsな存在ではない。
もちろん、最初からただのAlt Reactを探してたなら選択肢にしてもいいとは思ってて、個人的なおすすめは`hyperHTML`です。
ただそれでも、「(React|Vue|Xxx)はもう古い、これからはhyperHTMLだ!」とは言えないなーというのが正直なところ。
なぜかというと、普通に使うとライフサイクルフックもないしローカルなステートも持てないから。
それ用の実装を自分で作るか、`LitElement`とか`HyperHTMLElement`とかそういうのを使えばできるようになるけど、そうなるともう「Reactでよくない?」ってなると思う。
ファイルサイズとかTrasnpileの手間とかはあるけど、それでもあの完成度とか周辺機器の充実具合とか型とか含めると、ありあまるお釣りが来ると思う。
規模に応じてここを見極められる人だけが、その恩恵に与ればよいかなー。
WebComponentsの使いどころ
ウェブアプリケーションのドメインやら構成要素と切り離された、色味のないただの部品を作るための技術
このコンセプトから考えた、自分なりの有意義な使いどころをいくつか紹介。
textContentの操作
たとえば、
<format-date year="numeric" month="2-digit" day="2-digit" > 2018-06-26T03:07:51.446Z </format-date>
って書くとこうなるとか。
<span>2018/06/26</span>
試し書きしたコードの全体は以下Gistからどうぞ。
WebComponents to format date strings by Intl.DateTimeFormat() · GitHub
いわゆる`textContent`をラップして使うパターンで、HigherOrderWebComponentsとか勝手に言ってみたり?
そのほかにも、
ちょっと考えてもこれくらい出てきたので、このパターンはもっと色々作れそう。
このように機能的なHTMLの要素を作れるっていう捉え方をすると、割とイカした技術やん!ってなる。
が、結局これはただのDOM操作なので、素人が作るとすぐ魔窟と化すんやろなーっていう予感もある。
React以前の時代を生きたノウハウ aka 生DOMを扱う上でのお作法を知ってないといけないし、なんならjQueryも使えないそれ以前の、NativeのDOMのAPIを適切に操れる(パフォーマンスを気にしながら)スキルが必要なので。
Scoped CSS
ShadowDOMなので、CSSは`:host`セレクタが使える。
みんなが欲してやまないScoped CSSってやつができる。
これを使えば、単純なレイアウト目的のコンポーネントは置き換えられるとは思う。
class FlexBox extends HTMLElement { connectedCallback() { this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <style> :host { display: flex; } </style> <slot></slot> `; } }
こうすれば、
<flex-box> <div>flex-item!</div> <div>flex-item!</div> <div>flex-item!</div> </frex-box>
みたいなこともできる。
まあでもネストする場合にどうするんだとか、条件によってはこうしたいとか考え出すとスケールしないなーという感想。
結局のところ、要件がシンプルであるものにしか使えないのは一緒。
(ウェブアプリケーション作りのコンテキストの)Reactの`styled-component`とか比較しだすと勝ち目なんてないので、そこは比較したら敗け。
ちなみに
いわゆるWebComponentsでできたコンポーネントのカタログらしい。
今の時点で1581も登録されてるし、なにかすごい使いどころが見つかるかも?と思って見てみた俺の気持ちは以下です。
「Polymerカタログ」に改名しろ!