🧊

WebComponentsへの気持ち

この記事では、

  • ReactやVueではなくWebComponentsだけを使いたい気持ちを胸に、とある社内プロジェクトをやってみての学び
  • 巷にあふれるWebComponentsに対する見方への違和感

についてメモっておきます。

ただ「WebComponents」の語がもつ意味をきっちり定義してるわけではないので、そのへんは雰囲気で察してください。
そのせいで勘違いされがちな概念なんかも、知らんけど。

まずは巷にあふれる意見に対する気持ちから。

(React|Vue|Xxx)はもう古い、これからはWebComponentsだ!

勝手なイメージですが、こういう認識の人いるよね・・?

個人的には、「いや、WebComponentsはそういう技術じゃない」と思った・思ってます。
以下、ReactもVueもAngularも「ウェブアプリケーションを作るための技術」であるが、WebComponentsはそうじゃないという主張が続きます。

これはどっちかを選ぶようなもものでもない。
そもそもReactのドキュメントにも、共存する概念であるって書いてある。

Web Components - 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の存在意義って?

個人的には、「ウェブアプリケーションドメインやら構成要素と切り離された、色味のない汎用的な部品を作るための技術」かと。

もちろん属性は渡せるけど、そこで大きく挙動を変えたりはしない謙虚な部品。
そんな部品を、

  • いろんな場所で再利用する前提
  • 何にも依存せず

作りたい場合には、選ばれる技術なのかなーと。

Web Components を本番投入する(2018年春) - EagleLand

そういう点からすると、ここで書かれてる決済ボタンとかは良い例よねーと思った。
副産物とは思うけど、機能を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とか勝手に言ってみたり?

そのほかにも、

  • 数値の桁を丸めたり
  • Twitterのアカウントをリンクにしたり
  • コードブロックのシンタックスあてたり
  • `table`に合計の行や平均の列を追加したり

ちょっと考えてもこれくらい出てきたので、このパターンはもっと色々作れそう。
このように機能的なHTMLの要素を作れるっていう捉え方をすると、割とイカした技術やん!ってなる。

が、結局これはただのDOM操作なので、素人が作るとすぐ魔窟と化すんやろなーっていう予感もある。
React以前の時代を生きたノウハウ aka 生DOMを扱う上でのお作法を知ってないといけないし、なんならjQueryも使えないそれ以前の、NativeのDOMのAPIを適切に操れる(パフォーマンスを気にしながら)スキルが必要なので。

まあそれはjQueryプラグインでもReactコンポーネントでも、いつの時代でも一緒なんやけど。

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.org - Discuss & share web components

いわゆるWebComponentsでできたコンポーネントのカタログらしい。
今の時点で1581も登録されてるし、なにかすごい使いどころが見つかるかも?と思って見てみた俺の気持ちは以下です。

「Polymerカタログ」に改名しろ!

というわけで

いわゆるウェブアプリケーションを作ってるうちは、WebComponents最高!ってなる日は来ないと思ってる。

なにかあればコメント or リプライください!