リアクティブプロパティ

Litコンポーネントは、入力を受け取り、状態をJavaScriptのクラスフィールドまたはプロパティとして格納します。リアクティブプロパティは、変更されたときにリアクティブ更新サイクルをトリガーし、コンポーネントを再レンダリングし、オプションで属性の読み書きを行うことができるプロパティです。

Litは、リアクティブプロパティとその対応する属性を管理します。具体的には

  • リアクティブ更新。Litは、各リアクティブプロパティに対してゲッター/セッターペアを生成します。リアクティブプロパティが変更されると、コンポーネントは更新をスケジュールします。
  • 属性処理。デフォルトでは、Litはプロパティに対応する監視対象属性を設定し、属性が変更されたときにプロパティを更新します。プロパティ値は、オプションで属性に反映されることもあります。
  • スーパークラスのプロパティ。Litは、スーパークラスで宣言されたプロパティオプションを自動的に適用します。オプションを変更しない限り、プロパティを再宣言する必要はありません。
  • 要素のアップグレード。Litコンポーネントが要素が既にDOMにある後に定義されている場合、Litはアップグレードロジックを処理し、アップグレードされる前に要素に設定されたプロパティが、要素のアップグレード時に正しいリアクティブな副作用をトリガーするようにします。

公開プロパティは、コンポーネントの公開APIの一部です。一般的に、公開プロパティ、特に公開リアクティブプロパティは入力として扱う必要があります。

コンポーネントは、ユーザー入力に応じて以外は、独自の公開プロパティを変更すべきではありません。たとえば、メニューコンポーネントには、要素の所有者によって特定の値に初期化できる公開selectedプロパティがある場合がありますが、ユーザーがアイテムを選択したときにコンポーネント自体によって更新されます。このような場合、コンポーネントは、selectedプロパティが変更されたことをコンポーネントの所有者に知らせるためにイベントをディスパッチする必要があります。詳細はイベントのディスパッチを参照してください。

Litは、内部リアクティブ状態もサポートしています。内部リアクティブ状態とは、コンポーネントのAPIの一部ではないリアクティブプロパティを指します。これらのプロパティには対応する属性がなく、通常はTypeScriptではprotectedまたはprivateとしてマークされます。

コンポーネントは、独自の内部リアクティブ状態を操作します。場合によっては、内部リアクティブ状態が公開プロパティから初期化されることがあります。たとえば、ユーザーに見えるプロパティと内部状態の間で高価な変換がある場合などです。

公開リアクティブプロパティと同様に、内部リアクティブ状態の更新は更新サイクルをトリガーします。詳細は内部リアクティブ状態を参照してください。

要素の公開リアクティブプロパティは、デコレータまたは静的propertiesフィールドを使用して宣言します。

いずれの場合も、オプションオブジェクトを渡してプロパティの機能を構成できます。

クラスフィールド宣言で@propertyデコレータを使用して、リアクティブプロパティを宣言します。

@propertyデコレータの引数はオプションオブジェクトです。引数を省略すると、すべてのオプションのデフォルト値を指定することと同じです。

デコレータの使用。デコレータは提案されたJavaScript機能であるため、デコレータを使用するには、BabelやTypeScriptコンパイラなどのコンパイラを使用する必要があります。詳細はデコレータの有効化を参照してください。

静的プロパティクラスフィールドでのプロパティの宣言

“静的プロパティクラスフィールドでのプロパティの宣言”へのパーマリンク

静的propertiesクラスフィールドでプロパティを宣言するには

空のオプションオブジェクトは、すべてのオプションのデフォルト値を指定することと同じです。

プロパティ宣言時のクラスフィールドに関する問題の回避

“プロパティ宣言時のクラスフィールドに関する問題の回避”へのパーマリンク

クラスフィールドは、リアクティブプロパティと問題のある相互作用があります。クラスフィールドは要素インスタンス上に定義されますが、リアクティブプロパティは要素プロトタイプ上にアクセサとして定義されます。JavaScriptのルールによると、インスタンスプロパティはプロトタイププロパティよりも優先され、事実上隠蔽します。これは、クラスフィールドが使用されている場合、リアクティブプロパティアクセサが機能せず、プロパティの設定が要素の更新をトリガーしないことを意味します。

JavaScriptでは、リアクティブプロパティを宣言する際にクラスフィールドを使用しないでください。代わりに、プロパティは要素コンストラクタで初期化する必要があります。

あるいは、リアクティブプロパティを宣言するためにBabelを使用した標準デコレータを使用することもできます。

TypeScriptの場合、これらのパターンのいずれかを使用する限り、リアクティブプロパティの宣言にクラスフィールドを使用できます

  • フィールドにdeclareキーワードを追加し、フィールドのイニシャライザをコンストラクタに入れます。
  • 自動アクセサを使用するために、フィールドにaccessorキーワードを追加します。

オプションオブジェクトには、次のプロパティを含めることができます。

attribute

プロパティが属性に関連付けられているかどうか、または関連付けられた属性のカスタム名。デフォルト:true。attributeがfalseの場合、converterreflecttypeオプションは無視されます。詳細は属性名の設定を参照してください。

converter

プロパティと属性間の変換のためのカスタムコンバータ。指定されていない場合、デフォルトの属性コンバータを使用します。

hasChanged

プロパティが設定されるたびに、プロパティが変更され、更新をトリガーする必要があるかどうかを判断するために呼び出される関数。指定されていない場合、LitElementは厳密な不等式チェック(newValue !== oldValue)を使用して、プロパティ値が変更されたかどうかを判断します。詳細は変更検出のカスタマイズを参照してください。

noAccessor

デフォルトのプロパティアクセサの生成を回避するためにtrueに設定します。このオプションはめったに必要ありません。デフォルト:false。詳細はLitによるプロパティアクセサの生成の防止を参照してください。

reflect

プロパティ値が関連付けられた属性に反映されるかどうか。デフォルト:false。詳細は属性反映の有効化を参照してください。

state

プロパティを内部リアクティブ状態として宣言するためにtrueに設定します。内部リアクティブ状態は公開リアクティブプロパティと同様に更新をトリガーしますが、Litはそれに対する属性を生成せず、ユーザーはコンポーネントの外からアクセスできません。@stateデコレータを使用することと同じです。デフォルト:false。詳細は内部リアクティブ状態を参照してください。

type

文字列値の属性をプロパティに変換する場合、Litのデフォルトの属性コンバータは、指定された型に文字列を解析し、プロパティを属性に反映する際に逆の処理を行います。converterが設定されている場合、このフィールドはコンバータに渡されます。typeが指定されていない場合、デフォルトのコンバータはtype: Stringとして扱います。デフォルトコンバータの使用を参照してください。

TypeScriptを使用する場合、このフィールドは一般的にフィールドに対して宣言されたTypeScript型と一致する必要があります。ただし、typeオプションはLitのランタイムによって文字列のシリアル化/デシリアル化に使用され、型チェックメカニズムと混同しないでください。

オプションオブジェクトを省略するか、空のオプションオブジェクトを指定することは、すべてのオプションのデフォルト値を指定することと同じです。

内部リアクティブ状態とは、コンポーネントの公開APIの一部ではないリアクティブプロパティを指します。これらの状態プロパティには対応する属性がなく、コンポーネントの外から使用することを意図していません。内部リアクティブ状態は、コンポーネント自体によって設定する必要があります。

@stateデコレータを使用して、内部リアクティブ状態を宣言します。

静的propertiesクラスフィールドを使用して、state: trueオプションを使用して内部リアクティブ状態を宣言できます。

内部リアクティブ状態は、コンポーネントの外から参照しないでください。TypeScriptでは、これらのプロパティはprivateまたはprotectedとしてマークする必要があります。JavaScriptユーザー向けには、先頭のアンダースコア(_)などの規則を使用して、privateまたはprotectedプロパティを識別することをお勧めします。

内部リアクティブ状態は、プロパティに関連付けられた属性がない点を除いて、公開リアクティブプロパティと同じように機能します。内部リアクティブ状態に対して指定できるオプションは、hasChanged関数のみです。

@stateデコレータは、コードミニファイアに対して、プロパティ名をミニファイ中に変更できるというヒントとしても機能します。

プロパティの変更は、リアクティブ更新サイクルをトリガーし、コンポーネントがテンプレートを再レンダリングします。

プロパティが変更されると、次のシーケンスが発生します。

  1. プロパティのセッターが呼び出されます。
  2. セッターはコンポーネントのrequestUpdateメソッドを呼び出します。
  3. プロパティの古い値と新しい値が比較されます。
    • デフォルトでは、Litは値が変更されたかどうかを判定するために厳密な不等式テスト(つまり、newValue !== oldValue)を使用します。
    • プロパティにhasChanged関数が存在する場合、その関数はプロパティの古い値と新しい値を引数として呼び出されます。
  4. プロパティの変更が検出されると、非同期的に更新がスケジュールされます。既に更新がスケジュールされている場合は、更新は1回だけ実行されます。
  5. コンポーネントのupdateメソッドが呼び出され、変更されたプロパティが属性に反映され、コンポーネントのテンプレートが再レンダリングされます。

オブジェクトまたは配列のプロパティを直接変更しても、オブジェクト自体が変わっていないため、更新はトリガーされません。詳細については、オブジェクトと配列のプロパティの変更を参照してください。

リアクティブな更新サイクルにフックして変更するには、多くの方法があります。詳細については、リアクティブ更新サイクルを参照してください。

プロパティ変更検出の詳細については、変更検出のカスタマイズを参照してください。

オブジェクトまたは配列を変更しても、オブジェクト参照は変わらないため、更新はトリガーされません。オブジェクトと配列のプロパティは、次の2つの方法のいずれかで処理できます。

  • 不変データパターン。オブジェクトと配列を不変として扱います。たとえば、myArrayからアイテムを削除するには、新しい配列を作成します。

    この例は単純ですが、不変データを管理するためにImmerのようなライブラリを使用すると便利なことがよくあります。これにより、深くネストされたオブジェクトを設定する場合の複雑なボイラープレートコードを回避できます。

  • 更新を手動でトリガーする。データを変更し、requestUpdate()を呼び出して更新を直接トリガーします。例:

    引数を指定せずに呼び出された場合、requestUpdate()hasChanged()関数を呼び出さずに更新をスケジュールします。ただし、requestUpdate()現在のコンポーネントのみを更新することに注意してください。つまり、上記のコードを使用するコンポーネントが、this.myArrayをサブコンポーネントに渡す場合、サブコンポーネントは配列参照が変更されていないことを検出するため、更新されません。

一般的に、不変オブジェクトを使用したトップダウンのデータフローは、ほとんどのアプリケーションで最適です。これにより、新しい値をレンダリングする必要があるすべてのコンポーネントが確実にレンダリングされ(そして、データツリーの変更されていない部分は、それに依存するコンポーネントの更新を引き起こさないため、可能な限り効率的に実行されます)。

データを直接変更してrequestUpdate()を呼び出すことは、高度なユースケースと見なすべきです。この場合、(または他のシステムが)変更されたデータを使用するすべてのコンポーネントを特定し、各コンポーネントでrequestUpdate()を呼び出す必要があります。これらのコンポーネントがアプリケーション全体に分散されている場合、これは管理が困難になります。これをしっかりと行わないと、アプリケーションの2つの場所でレンダリングされるオブジェクトを変更しても、一方だけが更新される可能性があります。

特定のデータが単一のコンポーネントでのみ使用されることがわかっている単純なケースでは、必要であればデータを変更してrequestUpdate()を呼び出す方が安全です。

プロパティはJavaScriptデータを入力として受信するのに適していますが、属性はHTMLがマークアップから要素を構成することを許可する標準的な方法であり、プロパティを設定するためにJavaScriptを使用する必要はありません。リアクティブプロパティに対してプロパティと属性の両方のインターフェースを提供することは、クライアントサイドのテンプレートエンジンを使用せずにレンダリングされるもの(CMSから提供される静的HTMLページなど)を含む、幅広い環境でLitコンポーネントが役立つ重要な方法です。

デフォルトでは、Litは各公開リアクティブプロパティに対応する監視対象属性を設定し、属性が変更されるとプロパティを更新します。プロパティ値は、必要に応じて反映(属性に書き戻す)することもできます。

要素のプロパティは任意の型にすることができますが、属性は常に文字列です。これは、文字列以外のプロパティの監視対象属性反映属性に影響します。

  • 属性を監視する(属性からプロパティを設定する)には、属性値を文字列からプロパティ型に一致するように変換する必要があります。

  • 属性を反映する(プロパティから属性を設定する)には、プロパティ値を文字列に変換する必要があります。

属性を公開するブールプロパティは、falseをデフォルトにする必要があります。詳細については、ブール属性を参照してください。

デフォルトでは、Litはすべての公開リアクティブプロパティに対応する監視対象属性を作成します。監視対象属性の名前は、小文字にしたプロパティ名です。

異なる名前の監視対象属性を作成するには、attributeを文字列に設定します。

プロパティに対して監視対象属性の作成を防止するには、attributefalseに設定します。プロパティはマークアップの属性から初期化されず、属性の変更はそのプロパティに影響しません。

内部のリアクティブ状態には、関連付けられた属性がありません。

監視対象属性を使用して、マークアップからプロパティの初期値を提供できます。例:

Litには、StringNumberBooleanArrayObjectプロパティ型を処理するデフォルトコンバーターがあります。

デフォルトコンバーターを使用するには、プロパティ宣言でtypeオプションを指定します。

プロパティに型またはカスタムコンバーターを指定しない場合、type: Stringを指定した場合と同じように動作します。

以下の表は、デフォルトコンバーターが各型について変換をどのように処理するかを示しています。

属性からプロパティへ

変換
String対応する属性が要素に存在する場合は、プロパティを属性値に設定します。
Number対応する属性が要素に存在する場合は、プロパティをNumber(attributeValue)に設定します。
Boolean対応する属性が要素に存在する場合は、プロパティをtrueに設定します。
存在しない場合は、プロパティをfalseに設定します。
ObjectArray対応する属性が要素に存在する場合は、プロパティ値をJSON.parse(attributeValue)に設定します。

Booleanを除くすべてのケースで、対応する属性が要素に存在しない場合、プロパティはデフォルト値を保持するか、デフォルトが設定されていない場合はundefinedになります。

プロパティから属性へ

変換
StringNumberプロパティが定義されていてnull以外の場合、属性をプロパティ値に設定します。
プロパティがnullまたはundefinedの場合、属性を削除します。
Booleanプロパティがtruthyの場合、属性を作成し、その値を空文字列に設定します。
プロパティがfalsyの場合、属性を削除します。
ObjectArrayプロパティが定義されていてnull以外の場合、属性をJSON.stringify(propertyValue)に設定します。
プロパティがnullまたはundefinedの場合、属性を削除します。

プロパティ宣言でconverterオプションを使用して、カスタムプロパティコンバーターを指定できます。

converterはオブジェクトまたは関数にすることができます。オブジェクトの場合、fromAttributetoAttributeのキーを持つことができます。

converterが関数の場合は、fromAttributeの代わりに使用されます。

反映属性に対してtoAttribute関数が提供されていない場合、属性はデフォルトコンバーターを使用してプロパティ値に設定されます。

toAttributenullまたはundefinedを返す場合、属性は削除されます。

ブールプロパティを属性から構成可能にするには、**falseをデフォルトにする必要があります。**trueをデフォルトにすると、属性の有無にかかわらずtrueと等しいため、マークアップからfalseに設定できません。これは、Webプラットフォームの属性の標準的な動作です。

この動作がユースケースに合わない場合は、いくつかの選択肢があります。

  • プロパティ名を変更してfalseをデフォルトにします。たとえば、Webプラットフォームではenabledではなくdisabled属性(falseをデフォルト)を使用します。

  • 代わりに文字列値または数値値の属性を使用します。

プロパティを変更するたびに、その値が対応する属性に反映されるようにプロパティを構成できます。反映属性は、属性がCSSと、querySelectorなどのDOM APIから見えるため便利です。

例:

プロパティが変更されると、Litはデフォルトコンバーターの使用またはカスタムコンバーターの提供で説明されているように、対応する属性値を設定します。

属性は一般的に、要素自体ではなく所有者からの要素への入力と見なされるため、プロパティを属性に反映する操作は控えめにすべきです。現在、スタイリングやアクセシビリティなどのケースで必要ですが、:state擬似セレクターアクセシビリティオブジェクトモデルなど、プラットフォームがこれらのギャップを埋める機能を追加するにつれて、これは変わる可能性があります。

オブジェクトまたは配列型のプロパティの反映はお勧めしません。これにより、大きなオブジェクトがDOMにシリアル化され、パフォーマンスが低下する可能性があります。

Litは更新中に反映状態を追跡します。プロパティの変更が属性に反映され、属性の変更がプロパティを更新する場合、無限ループが発生する可能性があることに気づかれたかもしれません。ただし、Litはプロパティと属性が具体的に設定された時点を追跡して、これが発生しないようにします。

デフォルトでは、LitElementはすべてのリアクティブプロパティに対してゲッター/セッターペアを生成します。プロパティを設定するたびに、セッターが呼び出されます。

生成されたアクセサは、自動的にrequestUpdate()を呼び出し、更新がまだ開始されていない場合は更新を開始します。

プロパティの取得と設定の方法を指定するには、独自のゲッター/セッターペアを定義できます。例:

@propertyまたは@stateデコレータでカスタムプロパティアクセサを使用するには、上記の例のように、セッターにデコレータを配置します。@propertyまたは@stateデコレータ付きセッターはrequestUpdate()を呼び出します。

ほとんどの場合、カスタムプロパティアクセサを作成する必要はありません。既存のプロパティから値を計算するには、willUpdateコールバックを使用することをお勧めします。これにより、追加の更新をトリガーせずに更新サイクル中に値を設定できます。要素の更新後にカスタムアクションを実行するには、updatedコールバックを使用することをお勧めします。カスタムセッターは、ユーザーが設定する値を同期的に検証することが重要なまれなケースで使用できます。

クラスがプロパティに対して独自のアクセサを定義する場合、Litは生成されたアクセサでそれらを上書きしません。クラスがプロパティに対してアクセサを定義しない場合、スーパークラスがプロパティまたはアクセサを定義していても、Litはそれらを生成します。

まれに、サブクラスはスーパークラスに存在するプロパティのプロパティオプションを変更または追加する必要があります。

スーパークラスで定義されたアクセサを上書きするプロパティアクセサの生成をLitに防止するには、プロパティ宣言でnoAccessortrueに設定します。

独自のアクセサを定義する場合は、noAccessorを設定する必要はありません。

すべてのリアクティブプロパティには、プロパティが設定されたときに呼び出される関数hasChanged()があります。

hasChangedはプロパティの旧値と新値を比較し、プロパティが変更されたかどうかを評価します。hasChanged()がtrueを返す場合、Litは、まだスケジュールされていない場合、要素の更新を開始します。更新の詳細については、リアクティブ更新サイクルを参照してください。

hasChanged()のデフォルトの実装では、厳密な不等号比較を使用します。つまり、newVal !== oldValの場合、hasChanged()trueを返します。

プロパティのhasChanged()をカスタマイズするには、プロパティオプションとして指定します。

次の例では、hasChanged()は奇数値の場合のみtrueを返します。