リアクティブコントローラー
リアクティブコントローラーは、コンポーネントのリアクティブ更新サイクルにフックできるオブジェクトです。コントローラーは、機能に関連する状態と動作をバンドルし、複数のコンポーネント定義間で再利用可能にします。
コントローラーを使用して、独自のステートとコンポーネントのライフサイクルへのアクセスを必要とする機能を実装できます。たとえば、
- マウスイベントなどのグローバルイベントの処理
- ネットワーク経由でのデータ取得などの非同期タスクの管理
- アニメーションの実行
リアクティブコントローラーを使用すると、それ自体がコンポーネントではない小さな部品を組み合わせてコンポーネントを構築できます。それらは、独自のアイデンティティと状態を持つ、再利用可能な部分的なコンポーネント定義と考えることができます。
リアクティブコントローラーは、多くの点でクラスミックスインに似ています。主な違いは、独自のアイデンティティを持ち、コンポーネントのプロトタイプに追加しないことです。これにより、APIをカプセル化し、ホストコンポーネントごとに複数のコントローラーインスタンスを使用できます。コントローラーとミックスインの詳細を参照してください。
コントローラーの使用
“コントローラーの使用”へのパーマリンク各コントローラーには独自の生成APIがありますが、通常はインスタンスを作成してコンポーネントに格納します。
class MyElement extends LitElement {
private clock = new ClockController(this, 1000);
}
コントローラーインスタンスに関連付けられたコンポーネントは、ホストコンポーネントと呼ばれます。
コントローラーインスタンスは、ホストコンポーネントからライフサイクルコールバックを受信し、コントローラーに新しいレンダリングデータがある場合にホストの更新をトリガーします。これは、ClockController
の例で現在時刻が定期的にレンダリングされる仕組みです。
コントローラーは通常、ホストのrender()
メソッドで使用される機能を公開します。たとえば、多くのコントローラーは、現在の値のような状態を持ちます。
render() {
return html`
<div>Current time: ${this.clock.value}</div>
`;
}
各コントローラーには独自のAPIがあるため、使用方法については、特定のコントローラーのドキュメントを参照してください。
コントローラーの作成
“コントローラーの作成”へのパーマリンクリアクティブコントローラーは、ホストコンポーネントに関連付けられたオブジェクトであり、1つ以上のホストライフサイクルコールバックを実装するか、ホストと対話します。さまざまな方法で実装できますが、初期化のためのコンストラクターとライフサイクルのためのメソッドを使用してJavaScriptクラスを使用することに重点を置きます。
コントローラーの初期化
“コントローラーの初期化”へのパーマリンクコントローラーは、host.addController(this)
を呼び出すことで、ホストコンポーネントに自身を登録します。通常、コントローラーは後で対話できるように、ホストコンポーネントへの参照を格納します。
class ClockController implements ReactiveController {
private host: ReactiveControllerHost;
constructor(host: ReactiveControllerHost) {
// Store a reference to the host
this.host = host;
// Register for lifecycle updates
host.addController(this);
}
}
class ClockController {
constructor(host) {
// Store a reference to the host
this.host = host;
// Register for lifecycle updates
host.addController(this);
}
}
1回限りの設定のために、他のコンストラクターパラメーターを追加できます。
class ClockController implements ReactiveController {
private host: ReactiveControllerHost;
timeout: number
constructor(host: ReactiveControllerHost, timeout: number) {
this.host = host;
this.timeout = timeout;
host.addController(this);
}
class ClockController {
constructor(host, timeout) {
this.host = host;
this.timeout = timeout;
host.addController(this);
}
コントローラーがホストコンポーネントに登録されると、ライフサイクルコールバックやその他のクラスフィールドとメソッドをコントローラーに追加して、目的の状態と動作を実装できます。
ライフサイクル
“ライフサイクル”へのパーマリンクReactiveController
インターフェースで定義されているリアクティブコントローラーのライフサイクルは、リアクティブ更新サイクルのサブセットです。LitElementは、そのライフサイクルコールバック中にインストールされているコントローラーに呼び出します。これらのコールバックはオプションです。
hostConnected()
:- ホストが接続されたときに呼び出されます。
renderRoot
の作成後に呼び出されるため、この時点でシャドウルートが存在します。- イベントリスナー、オブザーバーなどの設定に役立ちます。
hostUpdate()
:- ホストの
update()
メソッドとrender()
メソッドの前に呼び出されます。 - 更新前にDOMを読み取るのに役立ちます(たとえば、アニメーションの場合)。
- ホストの
hostUpdated()
:- 更新後、ホストの
updated()
メソッドの前に呼び出されます。 - DOMが変更された後、DOMを読み取るのに役立ちます(たとえば、アニメーションの場合)。
- 更新後、ホストの
hostDisconnected()
:- ホストが切断されたときに呼び出されます。
hostConnected()
で追加されたもの(イベントリスナーやオブザーバーなど)の後処理に役立ちます。
詳細については、リアクティブ更新サイクルを参照してください。
コントローラーホストAPI
“コントローラーホストAPI”へのパーマリンクリアクティブコントローラーホストは、コントローラーの追加と更新の要求のための小さなAPIを実装し、コントローラーのライフサイクルメソッドを呼び出す責任があります。
これは、コントローラーホストで公開される最小限のAPIです。
addController(controller: ReactiveController)
removeController(controller: ReactiveController)
requestUpdate()
updateComplete: Promise<boolean>
HTMLElement
、ReactiveElement
、LitElement
に固有のコントローラーを作成し、これらのAPIをさらに必要とするコントローラー、または特定の要素クラスまたはその他のインターフェースに関連付けられたコントローラーを作成することもできます。
LitElement
とReactiveElement
はコントローラーホストですが、ホストは他のWebコンポーネントライブラリの基本クラス、フレームワークのコンポーネント、またはその他のコントローラーなどの他のオブジェクトでもあります。
他のコントローラーからのコントローラー構築
“他のコントローラーからのコントローラー構築”へのパーマリンクコントローラーは、他のコントローラーで構成することもできます。これを行うには、子コントローラーを作成し、ホストをそれに転送します。
class DualClockController implements ReactiveController {
private clock1: ClockController;
private clock2: ClockController;
constructor(host: ReactiveControllerHost, delay1: number, delay2: number) {
this.clock1 = new ClockController(host, delay1);
this.clock2 = new ClockController(host, delay2);
}
get time1() { return this.clock1.value; }
get time2() { return this.clock2.value; }
}
class DualClockController {
constructor(host, delay1, delay2) {
this.clock1 = new ClockController(host, delay1);
this.clock2 = new ClockController(host, delay2);
}
get time1() { return this.clock1.value; }
get time2() { return this.clock2.value; }
}
コントローラーとディレクティブ
“コントローラーとディレクティブ”へのパーマリンクコントローラーとディレクティブを組み合わせることは、特にレンダリングの前後に行う必要がある作業を行うディレクティブ(アニメーションディレクティブなど)、またはテンプレート内の特定の要素への参照が必要なコントローラーにとって非常に強力なテクニックです。
コントローラーとディレクティブを使用する主なパターンは2つあります。
- コントローラーディレクティブ。これらは、ホストのライフサイクルにフックするために、それ自体がコントローラーであるディレクティブです。
- ディレクティブを所有するコントローラー。これらは、ホストのテンプレートで使用するための1つ以上のディレクティブを作成するコントローラーです。
ディレクティブの作成の詳細については、カスタムディレクティブを参照してください。
コントローラーディレクティブ
“コントローラーディレクティブ”へのパーマリンクリアクティブコントローラーは、ホストのインスタンスフィールドとして格納する必要はありません。addController()
を使用してホストに追加されたものはすべてコントローラーです。特に、ディレクティブもコントローラーになることができます。これにより、ディレクティブはホストのライフサイクルにフックできます。
ディレクティブを所有するコントローラー
“ディレクティブを所有するコントローラー”へのパーマリンクディレクティブはスタンドアロン関数である必要はなく、コントローラーなどの他のオブジェクトのメソッドでもあります。これは、コントローラーがテンプレート内の特定の要素への参照を必要とする場合に役立ちます。
たとえば、ResizeObserverを使用して要素のサイズを観察できるResizeControllerを考えてみましょう。動作するには、ResizeControllerインスタンスと、観察する要素に配置されるディレクティブの両方が必要です。
class MyElement extends LitElement {
private _textSize = new ResizeController(this);
render() {
return html`
<textarea ${this._textSize.observe()}></textarea>
<p>The width is ${this._textSize.contentRect?.width}</p>
`;
}
}
class MyElement extends LitElement {
_textSize = new ResizeController(this);
render() {
return html`
<textarea ${this._textSize.observe()}></textarea>
<p>The width is ${this._textSize.contentRect?.width}</p>
`;
}
}
これを実装するには、ディレクティブを作成してメソッドから呼び出します。
class ResizeDirective {
/* ... */
}
const resizeDirective = directive(ResizeDirective);
export class ResizeController {
/* ... */
observe() {
// Pass a reference to the controller so the directive can
// notify the controller on size changes.
return resizeDirective(this);
}
}
TODO
- この例を見直して整理します。
ユースケース
“ユースケース”へのパーマリンクリアクティブコントローラーは非常に一般的であり、非常に幅広いユースケースがあります。ユーザー入力、状態管理、またはリモートAPIなどの外部リソースにコンポーネントを接続する場合に特に適しています。いくつかの一般的なユースケースを次に示します。
外部入力
“外部入力”へのパーマリンクリアクティブコントローラーを使用して、外部入力に接続できます。たとえば、キーボードとマウスのイベント、サイズオブザーバー、またはミューテーションオブザーバーなどです。コントローラーは、レンダリングで使用するための入力の現在の値を提供し、値が変更されたときにホストの更新を要求できます。
例:MouseMoveController
“例:MouseMoveController”へのパーマリンクこの例は、コントローラーがホストが接続および切断されたときにセットアップとクリーンアップ作業を実行し、入力が変更されたときに更新を要求する方法を示しています。
非同期タスク
“非同期タスク”へのパーマリンク長時間実行される計算やネットワークI/Oなどの非同期タスクは、通常、時間が経つにつれて変化する状態を持ち、タスクの状態が変更された(完了、エラーなど)ときにホストに通知する必要があります。
コントローラーは、タスク実行と状態をバンドルして、コンポーネント内で簡単に使用できるようにする優れた方法です。コントローラーとして記述されたタスクは、通常、ホストが設定できる入力と、ホストがレンダリングできる出力を持ちます。
@lit/task
には、ホストから入力を取得し、タスク関数を実行し、タスクの状態に応じて異なるテンプレートをレンダリングできる汎用的なTask
コントローラーが含まれています。
Task
を使用して、特定のタスクに合わせて調整されたAPIを持つカスタムコントローラーを作成できます。ここでは、デモREST APIから指定された名前のリストのいずれかをフェッチできるNamesController
でTask
をラップします。NameController
は入力としてkind
プロパティを公開し、タスクの状態に応じて4つのテンプレートのいずれかをレンダリングできるrender()
メソッドを公開します。タスクロジックとそのホストの更新方法は、ホストコンポーネントから抽象化されています。
TODO
- アニメーション