コンポーネントの構成

Litコードを複雑さを処理し、分離したユニットに分割する最も一般的な方法は、コンポーネントの構成です。これは、より小さく、より単純なコンポーネントから、より大きく、複雑なコンポーネントを構築するプロセスです。UIの画面を実装するタスクを割り当てられたと想像してください。

Screenshot of an application that displays a set of animal photos. The application has a top bar with a title ("Fuzzy") and a menu button. A left menu drawer is open, showing a set of options.

実装に複雑さが伴うであろう領域を特定できるでしょう。それらはコンポーネントになる可能性があります。

複雑さを特定のコンポーネントに分離することで、作業が大幅に簡素化され、これらのコンポーネントを組み合わせて全体的なデザインを作成できます。

たとえば、上記の比較的単純なスクリーンショットには、いくつかの可能なコンポーネントが含まれています。トップバー、メニューボタン、現在のセクションをナビゲートするためのメニュー項目を含むドロワー、およびメインコンテンツ領域です。これらはそれぞれコンポーネントで表現できます。ナビゲーションメニュー付きのドロワーのような複雑なコンポーネントは、ドロワー自体、ドロワーを開閉するボタン、メニュー、個々のメニュー項目など、多くの小さなコンポーネントに分割される場合があります。

Litでは、組み込みのHTML要素であろうとカスタム要素であろうと、テンプレートに要素を追加することで構成できます。

機能を分割する方法を決定する際に、新しいコンポーネントを作成する時期を特定するのに役立ついくつかの要因があります。UIの一部は、次のいずれかまたは複数の条件が当てはまる場合に、コンポーネントの適切な候補となる可能性があります。

  • 独自のステートを持っている。
  • 独自のテンプレートを持っている。
  • このコンポーネントまたは複数のコンポーネントのいずれかで、複数の場所で使用されている。
  • 一つのことをうまく行うことに焦点を当てている。
  • 明確に定義されたAPIを持っている。

ボタン、チェックボックス、入力フィールドなどの再利用可能なコントロールは、優れたコンポーネントになります。ただし、ドロワーやカルーセルなどのより複雑なUIも、コンポーネント化の優れた候補です。

サブコンポーネントとデータを交換する場合の一般的なルールは、DOMのモデルに従うことです。つまり、プロパティは下へイベントは上へです。

  • プロパティは下へ。サブコンポーネントのメソッドを呼び出すよりも、サブコンポーネントにプロパティを設定する方が通常は望ましいです。Litテンプレートやその他の宣言型テンプレートシステムでは、プロパティを簡単に設定できます。

  • イベントは上へ。Webプラットフォームでは、イベントを発火させることは、要素がツリー構造の上位に情報を送信するデフォルトの方法であり、多くの場合、ユーザーの操作に応答します。これにより、ホストコンポーネントはイベントに応答したり、ツリー構造のさらに上位の先祖のためにイベントを変換または再発火したりできます。

このモデルのいくつかの意味合い

  • コンポーネントは、そのシャドウDOM内のサブコンポーネントの信頼できる唯一の情報源である必要があります。サブコンポーネントは、ホストコンポーネントのプロパティを設定したり、メソッドを呼び出したりしないでください。

  • コンポーネントが自身のパブリックプロパティを変更する場合は、ツリー構造の上位のコンポーネントに通知するためのイベントを発火させる必要があります。一般的に、これらの変更は、ボタンを押したり、メニュー項目を選択したりするなど、ユーザーの操作の結果です。ユーザーが入力の値を変更したときにイベントを発火させるネイティブのinput要素を考えてみてください。

メニュー項目セットを含み、パブリックAPIの一部としてitemsおよびselectedItemプロパティを公開するメニューコンポーネントについて考えてみましょう。そのDOM構造は次のようになる可能性があります

A hierarchy of DOM nodes representing a menu. The top node, my-menu, has a ShadowRoot, which contains three my-item elements.

ユーザーがアイテムを選択すると、my-menu要素はselectedItemプロパティを更新する必要があります。また、選択が変更されたことを所有コンポーネントに通知するイベントも発火させる必要があります。完全なシーケンスは次のようになります。

  • ユーザーがアイテムを操作すると、イベントが発火します(clickなどの標準イベント、またはmy-itemコンポーネントに固有のイベント)。
  • my-menu要素はイベントを取得し、selectedItemプロパティを更新します。選択されたアイテムが強調表示されるように、何らかの状態を変更することもあります。
  • my-menu要素は、選択が変更されたことを示すセマンティックイベントを発火させます。このイベントは、たとえばselected-item-changedと呼ばれる可能性があります。このイベントはmy-menuのAPIの一部であるため、そのコンテキストで意味的に意味がある必要があります。

イベントのディスパッチとリッスンに関する詳細については、イベントを参照してください。

プロパティは下へ、イベントは上へは、開始するのに良いルールです。しかし、直接の子孫関係を持たない2つのコンポーネント間でデータを交換する必要がある場合はどうでしょうか?たとえば、シャドウツリー内の兄弟である2つのコンポーネント?

この問題の1つの解決策は、メディエーターパターンを使用することです。メディエーターパターンでは、ピアコンポーネントは互いに直接通信しません。代わりに、やり取りは第三者によって仲介されます。

メディエーターパターンを実装する簡単な方法は、所有コンポーネントに子要素からのイベントを処理させ、変更されたデータをツリー構造を介して下位に渡すことで、必要に応じて子要素の状態を更新させることです。メディエーターを追加することで、使い慣れたイベントは上へ、プロパティは下へという原則を使用して、ツリーを横断してデータを渡すことができます。

次の例では、メディエーター要素は、そのシャドウDOM内の入力要素とボタン要素からのイベントをリッスンします。入力にテキストがある場合にのみ、ユーザーが送信をクリックできるように、ボタンの有効状態を制御します。

その他のメディエーターパターンには、ストアが変更を仲介し、サブスクリプションを通じてコンポーネントを更新するflux/Reduxスタイルのパターンが含まれます。コンポーネントが変更を直接サブスクライブすることにより、親が子に必要なすべてのデータを渡す必要性を回避できます。

シャドウDOM内のノードに加えて、標準の<select>要素が子要素として<option>要素のセットを受け取り、メニュー項目としてレンダリングできるような、コンポーネントユーザーが提供する子ノードをレンダリングできます。

子ノードは、コンポーネントのシャドウDOMと区別するために「ライトDOM」と呼ばれることもあります。例:

ここで、top-bar要素には、ユーザーが提供する2つのライトDOMの子要素があります。ナビゲーションボタンとタイトルです。

ライトDOMの子要素とのやり取りは、シャドウDOM内のノードとのやり取りとは異なります。コンポーネントのシャドウDOM内のノードはコンポーネントによって管理され、コンポーネントの外部からアクセスすることはできません。ライトDOMの子要素はコンポーネントの外部から管理されますが、コンポーネントからもアクセスできます。コンポーネントのユーザーはいつでもライトDOMの子要素を追加または削除できるため、コンポーネントは静的な子ノードのセットを想定することはできません。

コンポーネントは、そのシャドウDOM内の<slot>要素を使用して、子ノードがレンダリングされるかどうか、および場所を制御できます。また、slotchangeイベントをリッスンすることで、子ノードが追加および削除されたときに通知を受け取ることができます。

詳細については、スロットを使用した子要素のレンダリングと、スロット付きの子要素へのアクセスに関するセクションを参照してください。

ミーアキャットの写真は、Anggit RizkiantoUnsplashで撮影したものです。