コンテキスト
コンテキストは、すべてのコンポーネントにプロパティを手動でバインドすることなく、データ全体をコンポーネントのサブツリーで使用できるようにする方法です。データは「コンテキスト的に」利用可能であり、データのプロバイダーとデータのコンシューマーの間にある祖先要素は、それを認識することさえありません。
Litのコンテキスト実装は、@lit/context
パッケージで利用できます。
npm i @lit/context
コンテキストは、アプリのデータストア、現在のユーザー、UIテーマなど、さまざまな種類の多数のコンポーネントで使用される必要があるデータ、または要素がライトDOMの子にデータを提供する必要がある場合など、データバインディングが不可能な場合に役立ちます。
コンテキストは、Reactのコンテキスト、またはAngularのような依存性注入システムと非常に似ていますが、DOMの動的な性質でコンテキストが機能し、さまざまなWebコンポーネントライブラリ、フレームワーク、プレーンJavaScript間での相互運用性を実現する重要な違いがあります。
コンテキストの使用には、コンテキストオブジェクト(キーと呼ばれることもあります)、プロバイダー、およびコンシューマーが含まれ、これらはコンテキストオブジェクトを使用して通信します。
コンテキストの定義(logger-context.ts
)
import {createContext} from '@lit/context';
import type {Logger} from 'my-logging-library';
export type {Logger} from 'my-logging-library';
export const loggerContext = createContext<Logger>('logger');
プロバイダー
import {LitElement, property, html} from 'lit';
import {provide} from '@lit/context';
import {Logger} from 'my-logging-library';
import {loggerContext} from './logger-context.js';
@customElement('my-app')
class MyApp extends LitElement {
@provide({context: loggerContext})
logger = new Logger();
render() {
return html`...`;
}
}
コンシューマー
import {LitElement, property} from 'lit';
import {consume} from '@lit/context';
import {type Logger, loggerContext} from './logger-context.js';
export class MyElement extends LitElement {
@consume({context: loggerContext})
@property({attribute: false})
public logger?: Logger;
private doThing() {
this.logger?.log('A thing was done');
}
}
主要概念
“主要概念”へのパーマリンクコンテキストプロトコル
“コンテキストプロトコル”へのパーマリンクLitのコンテキストは、W3CのWeb Components Community GroupによるContext Community Protocolに基づいています。
このプロトコルは、それらがどのように構築されたかに関係なく、要素間(または要素以外のコード間でも)の相互運用性を可能にします。コンテキストプロトコルを介して、Litベースの要素は、Litを使用して構築されていないコンシューマーにデータを提供したり、その逆もできます。
コンテキストプロトコルはDOMイベントに基づいています。コンシューマーは、必要なコンテキストキーを含むcontext-request
イベントを発生させ、それよりも上の要素はcontext-request
イベントをリッスンして、そのコンテキストキーのデータを提供できます。
@lit/context
はこのイベントベースのプロトコルを実装し、いくつかのリアクティブコントローラーとデコレーターを介して利用できるようにします。
コンテキストオブジェクト
“コンテキストオブジェクト”へのパーマリンクコンテキストは、コンテキストオブジェクトまたはコンテキストキーによって識別されます。これらは、コンテキストオブジェクトのアイデンティティによって共有される可能性のあるいくつかのデータを表すオブジェクトです。これらはMapキーと似ていると考えてください。
プロバイダー
“プロバイダー”へのパーマリンクプロバイダーは通常要素ですが(イベントハンドラーコードでもかまいません)、特定のコンテキストキーのデータを提供します。
コンシューマー
“コンシューマー”へのパーマリンクコンシューマーは、特定のコンテキストキーのデータに要求します。
サブスクリプション
“サブスクリプション”へのパーマリンクコンシューマーがコンテキストのデータに要求する際、コンテキストの変更をサブスクライブしたいことをプロバイダーに伝えることができます。プロバイダーに新しいデータがあると、コンシューマーに通知され、自動的に更新できます。
使用方法
“使用方法”へのパーマリンクコンテキストの定義
“コンテキストの定義”へのパーマリンクコンテキストの使用ごとに、データ要求を調整するためのコンテキストオブジェクトが必要です。このコンテキストオブジェクトは、提供されるデータのアイデンティティとタイプを表します。
コンテキストオブジェクトは、createContext()
関数を使用して作成されます。
export const myContext = createContext(Symbol('my-context'));
コンテキストオブジェクトを独自のモジュールに配置して、特定のプロバイダーとコンシューマーとは別にインポートできるようにすることをお勧めします。
コンテキストの型チェック
“コンテキストの型チェック”へのパーマリンクcreateContext()
は任意の値を受け取り、直接返します。TypeScriptでは、値は型付きのContext
オブジェクトにキャストされ、コンテキストの値の型を保持します。
このような間違いの場合
const myContext = createContext<Logger>(Symbol('logger'));
class MyElement extends LitElement {
@provide({context: myContext})
name: string
}
TypeScriptは、型string
が型Logger
に割り当てられないことを警告します。このチェックは現在、パブリックフィールドのみに対して行われます。
コンテキストの等価性
“コンテキストの等価性”へのパーマリンクコンテキストオブジェクトは、プロバイダーがコンテキスト要求イベントと値を照合するために使用されます。コンテキストは厳密な等価性(===
)で比較されるため、プロバイダーは、そのコンテキストキーが要求のコンテキストキーと等しい場合にのみ、コンテキスト要求を処理します。
つまり、コンテキストオブジェクトを作成する主な方法は2つあります。
- オブジェクト(
{}
)やシンボル(Symbol()
)など、グローバルに一意の値を使用する場合 - 文字列(
'logger'
)やグローバルシンボル(Symbol.for('logger')
)など、厳密な等価性の下で等しくなる可能性のある、グローバルに一意ではない値を使用する場合。
2つの別々のcreateContext()
呼び出しが同じコンテキストを参照する必要がある場合は、文字列など、厳密な等価性の下で等しくなるキーを使用します。
// true
createContext('my-context') === createContext('my-context')
ただし、アプリの2つのモジュールが、異なるオブジェクトを参照するために同じコンテキストキーを使用する可能性があることに注意してください。意図しない衝突を避けるには、'logger'
ではなく'console-logger'
など、比較的ユニークな文字列を使用することをお勧めします。
通常、グローバルに一意のコンテキストオブジェクトを使用するのが最適です。シンボルは、これを行う最も簡単な方法の1つです。
コンテキストの提供
“コンテキストの提供”へのパーマリンク@lit/context
では、コンテキスト値を提供する方法は2つあります。ContextProviderコントローラーと@provide()
デコレーターです。
@provide()
“@provide()”へのパーマリンク デコレーターを使用している場合は、@provide()
デコレーターが値を提供する最も簡単な方法です。これにより、ContextProviderコントローラーが自動的に作成されます。
@provide()
でプロパティをデコレートし、コンテキストキーを指定します。
import {LitElement, html} from 'lit';
import {property} from 'lit/decorators.js';
import {provide} from '@lit/context';
import {myContext, MyData} from './my-context.js';
class MyApp extends LitElement {
@provide({context: myContext})
myData: MyData;
}
@property()
または@state()
を使用してプロパティをリアクティブプロパティにすることもできます。これにより、プロパティを設定すると、プロバイダー要素とコンテキストコンシューマーの両方が更新されます。
@provide({context: myContext})
@property({attribute: false})
myData: MyData;
コンテキストプロパティは、多くの場合、プライベートにすることを目的としています。@state()
を使用してプライベートプロパティをリアクティブにすることができます。
@provide({context: myContext})
@state()
private _myData: MyData;
コンテキストプロパティをパブリックにすると、要素は子ツリーにパブリックフィールドを提供できます。
html`<my-provider-element .myData=${someData}>`
ContextProvider
“ContextProvider”へのパーマリンクContextProvider
は、context-request
イベントハンドラーを管理するリアクティブコントローラーです。
import {LitElement, html} from 'lit';
import {ContextProvider} from '@lit/context';
import {myContext} from './my-context.js';
export class MyApp extends LitElement {
private _provider = new ContextProvider(this, {context: myContext});
}
ContextProviderは、コンストラクターで初期値をオプションとして受け取ることができます。
private _provider = new ContextProvider(this, {context: myContext, initialValue: myData});
または、setValue()
を呼び出すことができます。
this._provider.setValue(myData);
コンテキストの使用
“コンテキストの使用”へのパーマリンク@consume()
デコレーター
“@consume() デコレーター”へのパーマリンク デコレーターを使用している場合は、@consume()
デコレーターが値を使用する最も簡単な方法です。これにより、ContextConsumerコントローラーが自動的に作成されます。
@consume()
でプロパティをデコレートし、コンテキストキーを指定します。
import {LitElement, html} from 'lit';
import {consume} from '@lit/context';
import {myContext, MyData} from './my-context.js';
class MyElement extends LitElement {
@consume({context: myContext})
myData: MyData;
}
この要素がドキュメントに接続されると、自動的にcontext-request
イベントが発生し、提供された値を取得してプロパティに割り当て、要素の更新をトリガーします。
ContextConsumer
“ContextConsumer”へのパーマリンクContextConsumerは、context-request
イベントのディスパッチを管理するリアクティブコントローラーです。このコントローラーは、新しい値が提供されるとホスト要素を更新します。提供された値は、コントローラーの.value
プロパティで利用できます。
import {LitElement, property} from 'lit';
import {ContextConsumer} from '@lit/context';
import {myContext} from './my-context.js';
export class MyElement extends LitElement {
private _myData = new ContextConsumer(this, {context: myContext});
render() {
const myData = this._myData.value;
return html`...`;
}
}
コンテキストへのサブスクライブ
“コンテキストの購読”へのパーマリンクコンシューマはコンテキストの値を購読できます。プロバイダに新しい値があると、購読しているすべてのコンシューマにその値を渡して更新させることができます。
@consume()
デコレータを使用して購読できます。
@consume({context: myContext, subscribe: true})
myData: MyData;
そして、ContextConsumer コントローラを使用します。
private _myData = new ContextConsumer(this,
{
context: myContext,
subscribe: true,
}
);
現在のユーザー、ロケールなど
“現在のユーザー、ロケールなど”へのパーマリンク最も一般的なコンテキストの使用例には、ページ全体でグローバルであり、ページ内のコンポーネントで必要となるのはごく一部の場合もあるデータが含まれます。コンテキストを使用しないと、ほとんどまたはすべてのコンポーネントで、データのリアクティブプロパティを受け入れて伝播する必要がある可能性があります。
サービス
“サービス”へのパーマリンクロガー、分析、データストアなどのアプリケーション全体のサービスは、コンテキストによって提供できます。共通モジュールからのインポートと比較したコンテキストの利点としては、コンテキストが提供する遅延結合とツリースコープがあります。テストでは、モックサービスを簡単に提供したり、ページの異なる部分に異なるサービスインスタンスを提供したりできます。
テーマは、ページ全体またはページ内のサブツリー全体に適用されるスタイルのセットです。これは、コンテキストが提供するデータのスコープの種類にまさに合致します。
テーマシステムを構築する1つの方法は、コンテナが提供できる名前付きスタイルを保持するTheme
型を定義することです。テーマを適用したい要素は、テーマオブジェクトを消費し、名前でスタイルを検索できます。カスタムテーマリアクティブコントローラは、ContextProviderとContextConsumerをラップして、ボイラープレートを削減できます。
HTMLベースのプラグイン
“HTMLベースのプラグイン”へのパーマリンクコンテキストを使用して、親からそのライトDOMの子にデータを渡すことができます。親は通常、ライトDOMの子を作成しないため、テンプレートベースのデータバインディングを使用してデータを渡すことはできませんが、context-request
イベントをリッスンして対応することはできます。
たとえば、さまざまな言語モードのプラグインを備えたコードエディタ要素を考えてみます。コンテキストを使用して機能を追加するためのプレーンHTMLシステムを作成できます。
<code-editor>
<code-editor-javascript-mode></code-editor-javascript-mode>
<code-editor-python-mode></code-editor-python-mode>
</code-editor>
この場合、<code-editor>
はコンテキストを介して言語モードを追加するためのAPIを提供し、プラグイン要素はそのAPIを消費してエディタに追加されます。
データフォーマッタ、リンクジェネレーターなど
“データフォーマッタ、リンクジェネレータなど”へのパーマリンク再利用可能なコンポーネントは、アプリケーション固有の方法でデータまたはURLをフォーマットする必要がある場合があります。たとえば、別のアイテムへのリンクをレンダリングするドキュメントビューアです。コンポーネントは、アプリケーションのURL空間を知りません。
これらの場合、コンポーネントは、アプリケーション固有のフォーマットをデータまたはリンクに適用するコンテキストで提供される関数に依存できます。
これらのAPIドキュメントは、生成されたAPIドキュメントが利用可能になるまでの概要です。
createContext()
“createContext()”へのパーマリンク 型付きのContextオブジェクトを作成します。
インポート:
import {createContext} from '@lit/context';
シグネチャ:
function createContext<ValueType, K = unknown>(key: K): Context<K, ValueType>;
コンテキストは、厳密な等価性で比較されます。
2つの異なるcreateContext()
呼び出しが同じコンテキストを参照するようにしたい場合は、文字列のような厳密な等価性の下で等しくなるキーをSymbol.for()
に使用します。
// true
createContext('my-context') === createContext('my-context')
// true
createContext(Symbol.for('my-context')) === createContext(Symbol.for('my-context'))
他のコンテキストと衝突しないことが保証されるようにコンテキストを一意にしたい場合は、Symbol()
やオブジェクトなど、厳密な等価性の下で一意のキーを使用します。
// false
createContext(Symbol('my-context')) === createContext(Symbol('my-context'))
// false
createContext({}) === createContext({})
ValueType
型パラメータは、このコンテキストによって提供できる値の型です。他のコンテキストAPIで正確な型を提供するために使用されます。
@provide()
“@provide()”へのパーマリンク コンポーネントにContextProviderコントローラを追加するプロパティデコレータであり、子コンシューマからのすべてのcontext-request
イベントに対応します。
インポート:
import {provide} from '@lit/context';
シグネチャ:
@provide({context: Context})
@consume()
“@consume()”へのパーマリンク コンテキストプロトコルを介してプロパティの値を取得するContextConsumerコントローラをコンポーネントに追加するプロパティデコレータです。
インポート:
import {consume} from '@lit/context';
シグネチャ:
@consume({context: Context, subscribe?: boolean})
subscribe
はデフォルトでfalse
です。コンテキストで提供された値の更新を購読するには、true
に設定します。
ContextProvider
“ContextProvider”へのパーマリンク context-request
イベントをリッスンすることにより、カスタム要素にコンテキストプロバイダの動作を追加するReactiveControllerです。
インポート:
import {ContextProvider} from '@lit/context';
コンストラクタ:
ContextProvider(
host: ReactiveElement,
options: {
context: T,
initialValue?: ContextType<T>
}
)
メンバー
setValue(v: T, force = false): void
提供された値を設定し、値が変更された場合、新しい値を購読しているすべてのコンシューマに通知します。
force
は、値が変更されなくても通知を行うため、オブジェクトに深いプロパティ変更があった場合に役立ちます。
ContextConsumer
“ContextConsumer”へのパーマリンク context-request
イベントをディスパッチすることにより、カスタム要素にコンテキスト消費の動作を追加するReactiveControllerです。
インポート:
import {ContextConsumer} from '@lit/context';
コンストラクタ:
ContextConsumer(
host: HostElement,
options: {
context: C,
callback?: (value: ContextType<C>, dispose?: () => void) => void,
subscribe?: boolean = false
}
)
メンバー
value: ContextType<C>
コンテキストの現在の値。
ホスト要素がドキュメントに接続されると、そのコンテキストキーを使用してcontext-request
イベントが発行されます。コンテキスト要求が満たされると、コントローラはコールバック(存在する場合)を呼び出し、ホストの更新をトリガーして、新しい値に対応できるようにします。
ホスト要素が切断されたときにも、プロバイダによって提供されたdisposeメソッドを呼び出します。
ContextRoot
“ContextRoot”へのパーマリンク ContextRootは、満たされていないコンテキスト要求を収集し、一致するコンテキストキーを満たす新しいプロバイダが利用可能になったときにそれらを再ディスパッチするために使用できます。これにより、コンシューマの後で、DOMツリーにプロバイダを追加したり、アップグレードしたりできます。
インポート:
import {ContextRoot} from '@lit/context';
コンストラクタ:
ContextRoot()
メンバー
attach(element: HTMLElement): void
ContextRootをこの要素にアタッチし、
context-request
イベントのリッスンを開始します。detach(element: HTMLElement): void
ContextRootをこの要素からデタッチし、
context-request
イベントのリッスンを停止します。
ContextRequestEvent
“ContextRequestEvent”へのパーマリンク コンシューマによってコンテキスト値を要求するために発生するイベントです。このイベントのAPIと動作は、コンテキストプロトコルで指定されています。
インポート:
import {ContextRequestEvent} from '@lit/context';
context-request
はバブルアップし、合成されます。
メンバー
readonly context: C
このイベントが値を要求しているコンテキストオブジェクト。
readonly callback: ContextCallback<ContextType<C>>
コンテキスト値を提供するために呼び出す関数。
readonly subscribe?: boolean
コンシューマが新しいコンテキスト値を購読するかどうか。
ContextCallback
“ContextCallback”へのパーマリンク コンテキスト要求者によって提供され、要求を満たす値を使用して呼び出されるコールバックです。
このコールバックは、要求された値が変更されると、コンテキストプロバイダによって複数回呼び出される可能性があります。
インポート:
import {type ContextCallback} from '@lit/context';
シグネチャ:
type ContextCallback<ValueType> = (
value: ValueType,
unsubscribe?: () => void
) => void;