本当はもっとやりたいことがある|デジハリ
本当はもっとやりたいことがある|デジハリ
2018.01.14

こんにちは、フロントエンドエンジニアのいなばです。 前回は、Custom ElementsのHTMLテンプレートを定義できる機能とShadow DOMを実際に動かしてみました。

今回は、Web Componentsを構成する要素のひとつであるCustom Elementsのライフサイクル フックを、簡単なデモを通して体験してみようと思います。

 

ライフサイクルフックの定義

Custom Elementsは、昨今のJavaScriptフレームワークと同じように、ライフサイクルフックを持っているようです。

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

簡単なデモとしてborder-radiusのジェネレーターもどきを作ってみたので、そちらで具体的な挙動を順番に確かめていくことにしましょう。

constructor

Class構文と同様に初期化のタイミングで呼ばれます。Documentに定義したカスタム要素を追加しなくても、new 演算子でインスタンスを作成したタイミングでは呼ばれるようです。

デモではshadowRootのHTMLの初期化と今回のカスタム要素で定義したメソッド内でのthisの解決を行なっています。

constructor() {
    super();
    this.attachShadow({
      mode: 'open'
    });
    this.render();
    this.updateRadius = this.updateRadius.bind(this);
  }

connectedCallbackとdisconnectedCallback

connectedCallbackはカスタム要素がDocumentに追加されたタイミングで発火します。 JavaScriptフレームワークに慣れしんでいる人向けに例えるならば、AngularならngOnInitとngOnDestroy、Vue.jsならmountedとdestroyedにそれぞれ相当するものと言えそうです。

デモでは、inputの変更を検知するためにaddEventListenerを呼んでいます。

connectedCallback() {
    this.shadowRoot.addEventListener('change', this.updateRadius);
  }

 

disconnectedCallbackは、カスタム要素がDocumentから削除されたタイミングで発火します。 カスタム要素が削除されたあともイベントリスナーが残り続けないようにイベントの削除を行っています。

disconnectedCallback() {
    this.shadowRoot.removeEventListener('change', this.updateRadius);
  }

attributeChangedCallback

カスタム要素のobservedAttributesで指定した属性が、追加、削除、更新、または置換されたときに発火します。

デモでは、レンジバーの変更時にvalueをradius属性に反映、属性の値が更新されたことによりattributeChangedCallbackが呼ばれ、結果HTMLの再描画を行っています。

// 監視する属性を指定
  static get observedAttributes() {
    return ['radius'];
  }

  get radius() {
    return this.getAttribute('radius');
  }

  set radius(val) {
    this.setAttribute('radius', val);
  }
  /* 省略 */
  connectedCallback() {
    this.shadowRoot.addEventListener('input', this.updateRadius);
  }

  disconnectedCallback() {
    this.shadowRoot.removeEventListener('input', this.updateRadius);
  }
  
  attributeChangedCallback(attrName, oldVal, newVal) {
    // shadowRootのHTMLを再描画する処理を呼ぶ
    this.render();
  }

  updateRadius(e) {
    this.radius = e.target.value;
  }

adoptedCallback

カスタム要素が新しいdocumentに移動されたときに発火する、とあるのですが、調べてみたもののどういうユースケースのために用意されたものなのかいまいちわかりませんでした(詳しい方いましたら是非教えてください)。

おまけ

先ほどのデモではinputの変更を検知した際に毎回Shadow Domを 丸ごと書き換えている ので、パフォーマンス観点からはあまり優しくない実装になっています。

この記事を執筆するにあたって情報収集するなかで、 lit-html というライブラリの存在を知り、試してみたデモがこちらです(ここからのデモはES modulesでライブラリを引っ張ってきているので、挙動の確認はChromeもしくはSafariでしてください)。

開発ツールを見ると、再描画の際に必要な箇所のみ更新されるようになっていることが確認できます。

lit-html
https://github.com/PolymerLabs/lit-html
Polymerを開発しているチームが開発している、効率的なDOMの描画を行うためのライブラリ

最後に、Ajaxでgithubリポジトリ一覧を取得して表示するコンポーネントも作ってみました。lit-htmlが持つrepeat関数を使って、リストの生成を行っています。

おわりに

簡単にですが、機能を持ったカスタム要素の実装を、実際に手を動かして確認することができました。コンポーネント単位でHTML、CSS、JavaScriptがひとまとまりになることで、見通しは抜群によくなりますね。

JavaScriptフレームワークに慣れ親しんでいると物足りなさも少し感じますが、コンポーネントの粒度など設計で気をつけることはあるものの、ページ数が多くて保守も長くなるようなWebサイトには適しているのではないかと感じています。

参考