ライフサイクル

Litコンポーネントは、標準的なカスタムエレメントのライフサイクルメソッドを使用します。さらに、Litは、リアクティブプロパティが変更されたときにDOMの変更をレンダリングするリアクティブ更新サイクルを導入しています。

Litコンポーネントは標準的なカスタムエレメントであり、カスタムエレメントのライフサイクルメソッドを継承します。カスタムエレメントのライフサイクルに関する情報は、MDNのライフサイクルコールバックの使用を参照してください。

標準的なカスタムエレメントのライフサイクルメソッドをカスタマイズする必要がある場合は、標準的なLitの機能を維持するために、必ずsuperの実装(例:super.connectedCallback())を呼び出してください。

要素が作成されたときに呼び出されます。また、既存の要素がアップグレードされたときにも呼び出されます。これは、カスタムエレメントの定義が、要素がすでにDOMにある後にロードされた場合に発生します。

requestUpdate()メソッドを使用して非同期更新を要求するため、Litコンポーネントがアップグレードされると、すぐに更新を実行します。

要素に既に設定されているプロパティを保存します。これにより、アップグレード前に設定された値が維持され、コンポーネントによって設定されたデフォルト値が正しく上書きされます。

最初の更新の前に実行する必要がある、一度限りの初期化タスクを実行します。たとえば、デコレータを使用しない場合、静的プロパティフィールドでプロパティを宣言するに示されているように、コンストラクタでプロパティのデフォルト値を設定できます。

コンポーネントがドキュメントのDOMに追加されたときに呼び出されます。

Litは、要素が接続された後に最初の要素更新サイクルを開始します。レンダリングの準備として、LitはrenderRoot(通常はshadowRoot)が作成されていることを確認します。

要素が少なくとも一度ドキュメントに接続されると、要素の接続状態に関係なく、コンポーネントの更新は続行されます。

connectedCallback()では、要素がドキュメントに接続されている場合にのみ発生する必要があるタスクを設定する必要があります。これらで最も一般的なのは、ウィンドウに追加されたkeydownイベントハンドラなど、要素の外部のノードにイベントリスナーを追加することです。通常、connectedCallback()で行われることは、要素が切断されたときに元に戻す必要があります。たとえば、メモリリークを防ぐためにウィンドウのイベントリスナーを削除します。

コンポーネントがドキュメントのDOMから削除されたときに呼び出されます。

リアクティブ更新サイクルを一時停止します。要素が接続されると再開されます。

このコールバックは、要素がもう使用されない可能性があることを要素に知らせる主要なシグナルです。そのため、disconnectedCallback()では、要素(要素の外部のノードに追加されたイベントリスナーなど)への参照を何も保持していないことを確認する必要があります。これにより、ガベージコレクションされることができます。要素は、DOM内の要素の移動やキャッシングの場合のように、切断された後に再接続される可能性があるため、そのような参照やリスナーは、connectedCallback()を介して再確立する必要がある場合があります。これにより、要素はこれらのシナリオで期待どおりに機能し続けます。たとえば、ウィンドウに追加されたkeydownイベントハンドラなど、要素の外部のノードのイベントリスナーを削除します。

内部イベントリスナーを削除する必要はありません。テンプレートで宣言的に追加されたものを含め、コンポーネント自身のDOMに追加されたイベントリスナーを削除する必要はありません。外部イベントリスナーとは異なり、これらはコンポーネントがガベージコレクションされるのを防ぎません。

要素のobservedAttributesのいずれかが変更されたときに呼び出されます。

Litは、このコールバックを使用して、属性の変更をリアクティブプロパティに同期します。具体的には、属性が設定されると、対応するプロパティが設定されます。Litは、コンポーネントのリアクティブプロパティのリストと一致するように、要素のobservedAttributes配列を自動的に設定します。

このコールバックを実装する必要があることはめったにありません。

コンポーネントが新しいドキュメントに移動されたときに呼び出されます。

adoptedCallbackはポリフィルされていないことに注意してください。

Litには、このコールバックに対するデフォルトの動作はありません。

このコールバックは、要素の動作がドキュメントが変更されたときに変更される必要がある高度なユースケースでのみ使用する必要があります。

標準的なカスタムエレメントのライフサイクルに加えて、Litコンポーネントはリアクティブ更新サイクルも実装しています。

リアクティブ更新サイクルは、リアクティブプロパティが変更された場合、またはrequestUpdate()メソッドが明示的に呼び出された場合にトリガーされます。Litは更新を非同期的に実行するため、プロパティの変更はバッチ処理されます。更新が要求された後、更新が開始する前にさらにプロパティが変更された場合、すべての変更は同じ更新で取得されます。

更新はマイクロタスクタイミングで行われます。つまり、ブラウザが次のフレームを画面にペイントする前に発生します。ブラウザのタイミングの詳細については、Jake Archibaldの記事を参照してください。

高いレベルでは、リアクティブ更新サイクルは次のとおりです。

  1. 1つ以上のプロパティが変更された場合、またはrequestUpdate()が呼び出された場合、更新がスケジュールされます。
  2. 次のフレームがペイントされる前に更新が実行されます。
    1. 反映属性が設定されます。
    2. コンポーネントのレンダリングメソッドが呼び出され、内部DOMが更新されます。
  3. 更新が完了し、updateCompleteプロミスが解決されます。

より詳細には、次のようになります。

更新前

更新

更新後

多くのリアクティブ更新メソッドは、変更されたプロパティのMapを受け取ります。Mapのキーはプロパティ名であり、その値は以前のプロパティ値です。現在のプロパティ値は、常にthis.propertyまたはthis[property]を使用して取得できます。

TypeScriptを使用していて、changedPropertiesマップの厳密な型チェックが必要な場合は、各プロパティ名に正しい型を推論するPropertyValues<this>を使用できます。

厳密な型付けをあまり気にしない場合、またはプロパティ名のみをチェックしていて、以前の値はチェックしない場合は、Map<string, any>などの制限の少ない型を使用できます。

PropertyValues<this>は、protectedまたはprivateプロパティを認識しません。protectedまたはprivateプロパティをチェックする場合は、制限の少ない型を使用する必要があります。

更新render()メソッドを含む)にプロパティを変更すると、changedPropertiesマップは更新されますが、新しい更新はトリガーされませんrender()の後(たとえば、updated()メソッド内)にプロパティを変更すると、新しい更新サイクルがトリガーされ、変更されたプロパティは次のサイクルで使用される新しいchangedPropertiesマップに追加されます。

リアクティブプロパティが変更された場合、またはrequestUpdate()メソッドが呼び出された場合、更新がトリガーされます。更新は非同期的に実行されるため、更新が実行される前に発生したすべての変更は、単一の更新のみになります。

リアクティブプロパティが設定されたときに呼び出されます。デフォルトでは、hasChanged()は厳密な等価性チェックを行い、trueを返す場合、更新がスケジュールされます。詳細については、hasChanged()の設定を参照してください。

明示的な更新をスケジュールするには、requestUpdate() を呼び出します。これは、プロパティに関連しない何かが変更されたときに要素を更新してレンダリングする必要がある場合に役立ちます。たとえば、タイマーコンポーネントは毎秒requestUpdate()を呼び出す場合があります。

変更されたプロパティのリストは、後続のライフサイクルメソッドに渡されるchangedPropertiesマップに格納されます。マップのキーはプロパティ名、値は以前のプロパティ値です。

オプションで、requestUpdate()を呼び出す際にプロパティ名と以前の値を渡すことができます。これは、changedPropertiesマップに格納されます。これは、プロパティのカスタムゲッターとセッターを実装する場合に役立ちます。カスタムゲッターとセッターの実装の詳細については、「リアクティブプロパティ」を参照してください。

更新が実行されると、performUpdate()メソッドが呼び出されます。このメソッドは、他のいくつかのライフサイクルメソッドを呼び出します。

通常は更新をトリガーする変更で、コンポーネントが更新されている**間**に発生したものは、**新しい更新をスケジュールしません**。これは、更新プロセス中にプロパティ値を計算できるようにするためです。更新中に変更されたプロパティは、changedPropertiesマップに**反映される**ため、後続のライフサイクルメソッドは変更内容を処理できます。

更新サイクルが必要かどうかを判断するために呼び出されます。

引数changedProperties: 変更されたプロパティの名前をキーとし、対応する以前の値を値とするMap
更新いいえ。このメソッド内のプロパティの変更は、要素の更新をトリガーしません。
super の呼び出し?不要です。
サーバー上で呼び出される?いいえ。

shouldUpdate()がデフォルトでtrueを返す場合、更新は通常どおりに進みます。falseを返す場合、更新サイクルの残りの部分は呼び出されませんが、updateComplete Promise は解決されたままです。

どのプロパティの変更が更新を引き起こすべきかを指定するために、shouldUpdate()を実装できます。changedPropertiesのマップを使用して、現在の値と以前の値を比較します。

更新中に必要な値を計算するために、update()の前に呼び出されます。

引数changedProperties: 変更されたプロパティの名前をキーとし、対応する以前の値を値とするMap
更新?いいえ。このメソッド内のプロパティの変更は、要素の更新をトリガーしません。
super の呼び出し?不要です。
サーバー上で呼び出される?はい。

他のプロパティに依存し、更新プロセスの残りの部分で使用されるプロパティ値を計算するには、willUpdate()を実装します。

コンポーネントのDOMを更新するために呼び出されます。

引数changedProperties: 変更されたプロパティの名前をキーとし、対応する以前の値を値とするMap
更新?いいえ。このメソッド内のプロパティの変更は、要素の更新をトリガーしません。
super の呼び出し?はい。super の呼び出しがないと、要素の属性とテンプレートは更新されません。
サーバー上で呼び出される?いいえ。

プロパティ値を属性に反映し、render()を呼び出してコンポーネントの内部DOMを更新します。

一般的に、このメソッドを実装する必要はありません。

update()によって呼び出され、コンポーネントのDOMをレンダリングするために使用されるレンダリング可能な結果(TemplateResultなど)を返すために実装する必要があります。

引数なし。
更新?いいえ。このメソッド内のプロパティの変更は、要素の更新をトリガーしません。
super の呼び出し?不要です。
サーバー上で呼び出される?はい。

render()メソッドには引数はありませんが、通常はコンポーネントのプロパティを参照します。詳細については、「レンダリング」を参照してください。

update()が呼び出されてコンポーネントのDOMへの変更がレンダリングされた後、これらのメソッドを使用してコンポーネントのDOMでアクションを実行できます。

updated()が呼び出される直前に、コンポーネントのDOMが初めて更新された後に呼び出されます。

引数changedProperties: 変更されたプロパティの名前をキーとし、対応する以前の値を値とするMap
更新?はい。このメソッド内のプロパティの変更は、新しい更新サイクルをスケジュールします。
super の呼び出し?不要です。
サーバー上で呼び出される?いいえ。

コンポーネントのDOMが作成された後、一度だけ作業を実行するには、firstUpdated()を実装します。例としては、特定のレンダリングされた要素にフォーカスを当てたり、要素にResizeObserverまたはIntersectionObserverを追加することがあります。

コンポーネントの更新が終了し、要素のDOMが更新およびレンダリングされるたびに呼び出されます。

引数changedProperties: 変更されたプロパティの名前をキーとし、対応する以前の値を値とするMap
更新?はい。このメソッド内のプロパティの変更は、要素の更新をトリガーします。
super の呼び出し?不要です。
サーバー上で呼び出される?いいえ。

更新後に要素DOMを使用するタスクを実行するには、updated()を実装します。たとえば、アニメーションを実行するコードは、要素DOMを測定する必要がある場合があります。

updateCompleteプロミスは、要素の更新が完了すると解決されます。更新を待つには、updateCompleteを使用します。解決された値は、要素の更新が完了したかどうかを示すブール値です。更新サイクルが終了した後、保留中の更新がない場合、trueになります。

要素が更新されると、その子要素も更新される場合があります。デフォルトでは、updateCompleteプロミスは要素の更新が完了したときに解決されますが、子要素の更新が完了するのを待ちません。getUpdateCompleteをオーバーライドすることで、この動作をカスタマイズできます。

要素の更新が完了した時点を知る必要があるいくつかのユースケースがあります。

  1. テスト テストを作成する場合、コンポーネントのDOMに関するアサーションを行う前に、updateCompleteプロミスを待機できます。アサーションがコンポーネントの全体の子ツリーの更新の完了に依存する場合は、Litのデフォルトのスケジューリングはブラウザーのmicrotaskキューを使用するため(アニメーションフレームの前に空になります)、requestAnimationFrameを待機する方が多くの場合適切です。これにより、requestAnimationFrameコールバックの前に、ページ上の保留中のすべてのLit更新が完了していることが保証されます。

  2. 測定 一部のコンポーネントでは、特定のレイアウトを実装するためにDOMを測定する必要があります。JavaScriptベースの測定ではなく、純粋なCSSを使用してレイアウトを実装する方が常に優れていますが、CSSの制限により避けられない場合があります。非常に単純なケースで、LitまたはReactiveElementコンポーネントを測定している場合は、状態の変更後および測定前にupdateCompleteを待機するだけで十分な場合があります。ただし、updateCompleteはすべての子孫の更新を待機しないため、レイアウトが変更されたときに測定コードをトリガーするより堅牢な方法として、ResizeObserverを使用することをお勧めします。

  3. イベント レンダリングが完了した後にコンポーネントからイベントをディスパッチする方が良い習慣です。そのため、イベントのリスナーはコンポーネントの完全にレンダリングされた状態を確認できます。これを行うには、イベントを発生させる前にupdateCompleteプロミスを待機できます。

更新サイクル中に未処理のエラーが発生すると、updateCompleteプロミスは拒否されます。詳細については、「更新サイクルでのエラーの処理」を参照してください。

render()またはupdate()などのライフサイクルメソッドで未処理の例外が発生すると、updateCompleteプロミスが拒否されます。例外をスローする可能性のあるライフサイクルメソッドにコードがある場合は、try/catchステートメント内に配置することをお勧めします。

updateCompleteプロミスを待機している場合も、try/catchを使用することをお勧めします。

場合によっては、予期しない場所でコードがスローされる場合があります。フォールバックとして、これらの問題をキャッチするためにwindow.onunhandledrejectionのハンドラーを追加できます。たとえば、これを使用して、再現が困難な問題の診断に役立つように、エラーをバックエンドサービスに報告できます。

このセクションでは、更新サイクルをカスタマイズするためのあまり一般的ではないメソッドについて説明します。

更新のタイミングをカスタマイズするには、scheduleUpdate()をオーバーライドします。scheduleUpdate()は更新が実行されようとしているときに呼び出され、デフォルトではすぐにperformUpdate()を呼び出します。更新を遅らせるためにオーバーライドします。この手法は、メインのレンダリング/イベントスレッドのブロックを解除するために使用できます。

たとえば、次のコードは、次のフレームの描画後に更新が実行されるようにスケジュールします。これは、更新にコストがかかる場合にジャンクを削減できます。

scheduleUpdate()をオーバーライドする場合は、保留中の更新を実行するためにsuper.scheduleUpdate()を呼び出す必要があります。

非同期関数はオプションです。

この例は、暗黙的にプロミスを返す非同期関数を示しています。scheduleUpdate()明示的にPromiseを返す関数として記述することもできます。どちらの場合も、scheduleUpdate()によって返されたプロミスが解決されるまで、**次の**更新は開始されません。

shouldUpdate()update()updated()などの他のメソッドを呼び出すことで、リアクティブな更新サイクルを実装します。

保留中の更新をすぐに処理するには、performUpdate()を呼び出します。これは一般的に必要ありませんが、同期的に更新する必要があるまれなケースでは実行できます。(保留中の更新がない場合は、requestUpdate()を呼び出してからperformUpdate()を呼び出すことで、強制的に同期更新を行うことができます。)

スケジューリングをカスタマイズするには、scheduleUpdate()を使用します。

更新のスケジュール方法をカスタマイズする場合は、scheduleUpdate()をオーバーライドします。以前は、この目的のためにperformUpdate()をオーバーライドすることをお勧めしていました。それは引き続き機能しますが、保留中の更新を同期的に処理するためにperformUpdate()を呼び出すことをより困難にします。

hasUpdatedプロパティは、コンポーネントが少なくとも1回更新された場合にtrueを返します。コンポーネントがまだ更新されていない場合にのみ作業を実行するには、ライフサイクルメソッドのいずれかでhasUpdatedを使用できます。

updateCompleteプロミスを満たす前に追加の条件を待機するには、getUpdateComplete()メソッドをオーバーライドします。たとえば、子要素の更新を待機することが役立つ場合があります。最初にsuper.getUpdateComplete()を待機してから、後続の状態を待機します。

TypeScriptのES5出力を使用しているユーザーとの互換性を確保するために、updateCompleteゲッターではなくgetUpdateComplete()メソッドをオーバーライドすることをお勧めします(TypeScript#338を参照)。

コンポーネントクラスがライフサイクルコールバックを実装することに加えて、デコレーターなどの外部コードがコンポーネントのライフサイクルにフックする必要がある場合があります。

Litは、外部コードがリアクティブな更新ライフサイクルと統合するための2つの概念を提供します。static addInitializer()addController()です。

addInitializer()を使用すると、Litクラス定義にアクセスできるコードは、クラスのインスタンスが構築されるときにコードを実行できます。

これは、カスタムデコレーターを作成する場合に非常に役立ちます。デコレーターはクラス定義時に実行され、フィールドとメソッドの定義を置き換えるなどの処理を行うことができます。インスタンスが作成されたときに作業を行う必要がある場合も、addInitializer()を呼び出す必要があります。これは、リアクティブコントローラーを追加するために一般的に使用されるため、デコレーターはコンポーネントのライフサイクルにフックできます。

フィールドをデコレートすると、各インスタンスでコントローラーを追加するイニシャライザーが実行されます。

イニシャライザーはコンストラクターごとに保存されます。サブクラスにイニシャライザーを追加しても、スーパークラスに追加されません。イニシャライザーはコンストラクターで実行されるため、イニシャライザーはスーパークラスから開始してインスタンスのクラスに進むというクラス階層の順序で実行されます。

addController()は、Litコンポーネントにリアクティブコントローラーを追加して、コンポーネントがコントローラーのライフサイクルコールバックを呼び出すようにします。リアクティブコントローラーのドキュメントで詳細を確認してください。

removeController()は、リアクティブコントローラーを削除するため、このコンポーネントからライフサイクルコールバックを受け取らなくなります。

Litのサーバーサイドレンダリングパッケージは現在開発中であるため、以下の情報は変更される可能性があります。

サーバーでLitをレンダリングする場合、更新サイクル全体が呼び出されるわけではありません。サーバーで呼び出されるメソッドを以下に示します。