コンテキスト

コンテキストは、すべてのコンポーネントにプロパティを手動でバインドすることなく、データ全体をコンポーネントのサブツリーで使用できるようにする方法です。データは「コンテキスト的に」利用可能であり、データのプロバイダーとデータのコンシューマーの間にある祖先要素は、それを認識することさえありません。

Litのコンテキスト実装は、@lit/contextパッケージで利用できます。

コンテキストは、アプリのデータストア、現在のユーザー、UIテーマなど、さまざまな種類の多数のコンポーネントで使用される必要があるデータ、または要素がライトDOMの子にデータを提供する必要がある場合など、データバインディングが不可能な場合に役立ちます。

コンテキストは、Reactのコンテキスト、またはAngularのような依存性注入システムと非常に似ていますが、DOMの動的な性質でコンテキストが機能し、さまざまなWebコンポーネントライブラリ、フレームワーク、プレーンJavaScript間での相互運用性を実現する重要な違いがあります。

コンテキストの使用には、コンテキストオブジェクト(キーと呼ばれることもあります)、プロバイダー、およびコンシューマーが含まれ、これらはコンテキストオブジェクトを使用して通信します。

コンテキストの定義(logger-context.ts

プロバイダー

コンシューマー

Litのコンテキストは、W3CのWeb Components Community GroupによるContext Community Protocolに基づいています。

このプロトコルは、それらがどのように構築されたかに関係なく、要素間(または要素以外のコード間でも)の相互運用性を可能にします。コンテキストプロトコルを介して、Litベースの要素は、Litを使用して構築されていないコンシューマーにデータを提供したり、その逆もできます。

コンテキストプロトコルはDOMイベントに基づいています。コンシューマーは、必要なコンテキストキーを含むcontext-requestイベントを発生させ、それよりも上の要素はcontext-requestイベントをリッスンして、そのコンテキストキーのデータを提供できます。

@lit/contextはこのイベントベースのプロトコルを実装し、いくつかのリアクティブコントローラーとデコレーターを介して利用できるようにします。

コンテキストは、コンテキストオブジェクトまたはコンテキストキーによって識別されます。これらは、コンテキストオブジェクトのアイデンティティによって共有される可能性のあるいくつかのデータを表すオブジェクトです。これらはMapキーと似ていると考えてください。

プロバイダーは通常要素ですが(イベントハンドラーコードでもかまいません)、特定のコンテキストキーのデータを提供します。

コンシューマーは、特定のコンテキストキーのデータに要求します。

コンシューマーがコンテキストのデータに要求する際、コンテキストの変更をサブスクライブしたいことをプロバイダーに伝えることができます。プロバイダーに新しいデータがあると、コンシューマーに通知され、自動的に更新できます。

コンテキストの使用ごとに、データ要求を調整するためのコンテキストオブジェクトが必要です。このコンテキストオブジェクトは、提供されるデータのアイデンティティとタイプを表します。

コンテキストオブジェクトは、createContext()関数を使用して作成されます。

コンテキストオブジェクトを独自のモジュールに配置して、特定のプロバイダーとコンシューマーとは別にインポートできるようにすることをお勧めします。

createContext()は任意の値を受け取り、直接返します。TypeScriptでは、値は型付きのContextオブジェクトにキャストされ、コンテキストのの型を保持します。

このような間違いの場合

TypeScriptは、型stringが型Loggerに割り当てられないことを警告します。このチェックは現在、パブリックフィールドのみに対して行われます。

コンテキストオブジェクトは、プロバイダーがコンテキスト要求イベントと値を照合するために使用されます。コンテキストは厳密な等価性(===)で比較されるため、プロバイダーは、そのコンテキストキーが要求のコンテキストキーと等しい場合にのみ、コンテキスト要求を処理します。

つまり、コンテキストオブジェクトを作成する主な方法は2つあります。

  1. オブジェクト({})やシンボル(Symbol())など、グローバルに一意の値を使用する場合
  2. 文字列('logger')やグローバルシンボル(Symbol.for('logger'))など、厳密な等価性の下で等しくなる可能性のある、グローバルに一意ではない値を使用する場合。

2つの別々のcreateContext()呼び出しが同じコンテキストを参照する必要がある場合は、文字列など、厳密な等価性の下で等しくなるキーを使用します。

ただし、アプリの2つのモジュールが、異なるオブジェクトを参照するために同じコンテキストキーを使用する可能性があることに注意してください。意図しない衝突を避けるには、'logger'ではなく'console-logger'など、比較的ユニークな文字列を使用することをお勧めします。

通常、グローバルに一意のコンテキストオブジェクトを使用するのが最適です。シンボルは、これを行う最も簡単な方法の1つです。

@lit/contextでは、コンテキスト値を提供する方法は2つあります。ContextProviderコントローラーと@provide()デコレーターです。

デコレーターを使用している場合は、@provide()デコレーターが値を提供する最も簡単な方法です。これにより、ContextProviderコントローラーが自動的に作成されます。

@provide()でプロパティをデコレートし、コンテキストキーを指定します。

@property()または@state()を使用してプロパティをリアクティブプロパティにすることもできます。これにより、プロパティを設定すると、プロバイダー要素とコンテキストコンシューマーの両方が更新されます。

コンテキストプロパティは、多くの場合、プライベートにすることを目的としています。@state()を使用してプライベートプロパティをリアクティブにすることができます。

コンテキストプロパティをパブリックにすると、要素は子ツリーにパブリックフィールドを提供できます。

ContextProviderは、context-requestイベントハンドラーを管理するリアクティブコントローラーです。

ContextProviderは、コンストラクターで初期値をオプションとして受け取ることができます。

または、setValue()を呼び出すことができます。

デコレーターを使用している場合は、@consume()デコレーターが値を使用する最も簡単な方法です。これにより、ContextConsumerコントローラーが自動的に作成されます。

@consume()でプロパティをデコレートし、コンテキストキーを指定します。

この要素がドキュメントに接続されると、自動的にcontext-requestイベントが発生し、提供された値を取得してプロパティに割り当て、要素の更新をトリガーします。

ContextConsumerは、context-requestイベントのディスパッチを管理するリアクティブコントローラーです。このコントローラーは、新しい値が提供されるとホスト要素を更新します。提供された値は、コントローラーの.valueプロパティで利用できます。

コンテキストへのサブスクライブ

“コンテキストの購読”へのパーマリンク

コンシューマはコンテキストの値を購読できます。プロバイダに新しい値があると、購読しているすべてのコンシューマにその値を渡して更新させることができます。

@consume() デコレータを使用して購読できます。

そして、ContextConsumer コントローラを使用します。

最も一般的なコンテキストの使用例には、ページ全体でグローバルであり、ページ内のコンポーネントで必要となるのはごく一部の場合もあるデータが含まれます。コンテキストを使用しないと、ほとんどまたはすべてのコンポーネントで、データのリアクティブプロパティを受け入れて伝播する必要がある可能性があります。

ロガー、分析、データストアなどのアプリケーション全体のサービスは、コンテキストによって提供できます。共通モジュールからのインポートと比較したコンテキストの利点としては、コンテキストが提供する遅延結合とツリースコープがあります。テストでは、モックサービスを簡単に提供したり、ページの異なる部分に異なるサービスインスタンスを提供したりできます。

テーマは、ページ全体またはページ内のサブツリー全体に適用されるスタイルのセットです。これは、コンテキストが提供するデータのスコープの種類にまさに合致します。

テーマシステムを構築する1つの方法は、コンテナが提供できる名前付きスタイルを保持するTheme型を定義することです。テーマを適用したい要素は、テーマオブジェクトを消費し、名前でスタイルを検索できます。カスタムテーマリアクティブコントローラは、ContextProviderとContextConsumerをラップして、ボイラープレートを削減できます。

コンテキストを使用して、親からそのライトDOMの子にデータを渡すことができます。親は通常、ライトDOMの子を作成しないため、テンプレートベースのデータバインディングを使用してデータを渡すことはできませんが、context-requestイベントをリッスンして対応することはできます。

たとえば、さまざまな言語モードのプラグインを備えたコードエディタ要素を考えてみます。コンテキストを使用して機能を追加するためのプレーンHTMLシステムを作成できます。

この場合、<code-editor>はコンテキストを介して言語モードを追加するためのAPIを提供し、プラグイン要素はそのAPIを消費してエディタに追加されます。

“データフォーマッタ、リンクジェネレータなど”へのパーマリンク

再利用可能なコンポーネントは、アプリケーション固有の方法でデータまたはURLをフォーマットする必要がある場合があります。たとえば、別のアイテムへのリンクをレンダリングするドキュメントビューアです。コンポーネントは、アプリケーションのURL空間を知りません。

これらの場合、コンポーネントは、アプリケーション固有のフォーマットをデータまたはリンクに適用するコンテキストで提供される関数に依存できます。

これらのAPIドキュメントは、生成されたAPIドキュメントが利用可能になるまでの概要です。

型付きのContextオブジェクトを作成します。

インポート:

シグネチャ:

コンテキストは、厳密な等価性で比較されます。

2つの異なるcreateContext()呼び出しが同じコンテキストを参照するようにしたい場合は、文字列のような厳密な等価性の下で等しくなるキーをSymbol.for()に使用します。

他のコンテキストと衝突しないことが保証されるようにコンテキストを一意にしたい場合は、Symbol()やオブジェクトなど、厳密な等価性の下で一意のキーを使用します。

ValueType型パラメータは、このコンテキストによって提供できる値の型です。他のコンテキストAPIで正確な型を提供するために使用されます。

コンポーネントにContextProviderコントローラを追加するプロパティデコレータであり、子コンシューマからのすべてのcontext-requestイベントに対応します。

インポート:

シグネチャ:

コンテキストプロトコルを介してプロパティの値を取得するContextConsumerコントローラをコンポーネントに追加するプロパティデコレータです。

インポート:

シグネチャ:

subscribeはデフォルトでfalseです。コンテキストで提供された値の更新を購読するには、trueに設定します。

context-requestイベントをリッスンすることにより、カスタム要素にコンテキストプロバイダの動作を追加するReactiveControllerです。

インポート:

コンストラクタ:

メンバー

  • setValue(v: T, force = false): void

    提供された値を設定し、値が変更された場合、新しい値を購読しているすべてのコンシューマに通知します。forceは、値が変更されなくても通知を行うため、オブジェクトに深いプロパティ変更があった場合に役立ちます。

context-requestイベントをディスパッチすることにより、カスタム要素にコンテキスト消費の動作を追加するReactiveControllerです。

インポート:

コンストラクタ:

メンバー

  • value: ContextType<C>

    コンテキストの現在の値。

ホスト要素がドキュメントに接続されると、そのコンテキストキーを使用してcontext-requestイベントが発行されます。コンテキスト要求が満たされると、コントローラはコールバック(存在する場合)を呼び出し、ホストの更新をトリガーして、新しい値に対応できるようにします。

ホスト要素が切断されたときにも、プロバイダによって提供されたdisposeメソッドを呼び出します。

ContextRootは、満たされていないコンテキスト要求を収集し、一致するコンテキストキーを満たす新しいプロバイダが利用可能になったときにそれらを再ディスパッチするために使用できます。これにより、コンシューマの後で、DOMツリーにプロバイダを追加したり、アップグレードしたりできます。

インポート:

コンストラクタ:

メンバー

  • attach(element: HTMLElement): void

    ContextRootをこの要素にアタッチし、context-requestイベントのリッスンを開始します。

  • detach(element: HTMLElement): void

    ContextRootをこの要素からデタッチし、context-requestイベントのリッスンを停止します。

コンシューマによってコンテキスト値を要求するために発生するイベントです。このイベントのAPIと動作は、コンテキストプロトコルで指定されています。

インポート:

context-requestはバブルアップし、合成されます。

メンバー

  • readonly context: C

    このイベントが値を要求しているコンテキストオブジェクト。

  • readonly callback: ContextCallback<ContextType<C>>

    コンテキスト値を提供するために呼び出す関数。

  • readonly subscribe?: boolean

    コンシューマが新しいコンテキスト値を購読するかどうか。

コンテキスト要求者によって提供され、要求を満たす値を使用して呼び出されるコールバックです。

このコールバックは、要求された値が変更されると、コンテキストプロバイダによって複数回呼び出される可能性があります。

インポート:

シグネチャ: