リアクティブプロパティ
Litコンポーネントは、入力を受け取り、状態をJavaScriptのクラスフィールドまたはプロパティとして格納します。リアクティブプロパティは、変更されたときにリアクティブ更新サイクルをトリガーし、コンポーネントを再レンダリングし、オプションで属性の読み書きを行うことができるプロパティです。
class MyElement extends LitElement {
@property()
name?: string;
}
class MyElement extends LitElement {
static properties = {
name: {},
};
}
Litは、リアクティブプロパティとその対応する属性を管理します。具体的には
- リアクティブ更新。Litは、各リアクティブプロパティに対してゲッター/セッターペアを生成します。リアクティブプロパティが変更されると、コンポーネントは更新をスケジュールします。
- 属性処理。デフォルトでは、Litはプロパティに対応する監視対象属性を設定し、属性が変更されたときにプロパティを更新します。プロパティ値は、オプションで属性に反映されることもあります。
- スーパークラスのプロパティ。Litは、スーパークラスで宣言されたプロパティオプションを自動的に適用します。オプションを変更しない限り、プロパティを再宣言する必要はありません。
- 要素のアップグレード。Litコンポーネントが要素が既にDOMにある後に定義されている場合、Litはアップグレードロジックを処理し、アップグレードされる前に要素に設定されたプロパティが、要素のアップグレード時に正しいリアクティブな副作用をトリガーするようにします。
公開プロパティと内部状態
“公開プロパティと内部状態”へのパーマリンク公開プロパティは、コンポーネントの公開APIの一部です。一般的に、公開プロパティ、特に公開リアクティブプロパティは入力として扱う必要があります。
コンポーネントは、ユーザー入力に応じて以外は、独自の公開プロパティを変更すべきではありません。たとえば、メニューコンポーネントには、要素の所有者によって特定の値に初期化できる公開selected
プロパティがある場合がありますが、ユーザーがアイテムを選択したときにコンポーネント自体によって更新されます。このような場合、コンポーネントは、selected
プロパティが変更されたことをコンポーネントの所有者に知らせるためにイベントをディスパッチする必要があります。詳細はイベントのディスパッチを参照してください。
Litは、内部リアクティブ状態もサポートしています。内部リアクティブ状態とは、コンポーネントのAPIの一部ではないリアクティブプロパティを指します。これらのプロパティには対応する属性がなく、通常はTypeScriptではprotectedまたはprivateとしてマークされます。
@state()
private _counter = 0;
static properties = {
_counter: {state: true}
};
constructor() {
super();
this._counter = 0;
}
コンポーネントは、独自の内部リアクティブ状態を操作します。場合によっては、内部リアクティブ状態が公開プロパティから初期化されることがあります。たとえば、ユーザーに見えるプロパティと内部状態の間で高価な変換がある場合などです。
公開リアクティブプロパティと同様に、内部リアクティブ状態の更新は更新サイクルをトリガーします。詳細は内部リアクティブ状態を参照してください。
公開リアクティブプロパティ
“公開リアクティブプロパティ”へのパーマリンク要素の公開リアクティブプロパティは、デコレータまたは静的properties
フィールドを使用して宣言します。
いずれの場合も、オプションオブジェクトを渡してプロパティの機能を構成できます。
デコレータによるプロパティの宣言
“デコレータによるプロパティの宣言”へのパーマリンククラスフィールド宣言で@property
デコレータを使用して、リアクティブプロパティを宣言します。
class MyElement extends LitElement {
@property({type: String})
mode?: string;
@property({attribute: false})
data = {};
}
@property
デコレータの引数はオプションオブジェクトです。引数を省略すると、すべてのオプションのデフォルト値を指定することと同じです。
デコレータの使用。デコレータは提案されたJavaScript機能であるため、デコレータを使用するには、BabelやTypeScriptコンパイラなどのコンパイラを使用する必要があります。詳細はデコレータの有効化を参照してください。
静的プロパティクラスフィールドでのプロパティの宣言
“静的プロパティクラスフィールドでのプロパティの宣言”へのパーマリンク静的properties
クラスフィールドでプロパティを宣言するには
class MyElement extends LitElement {
static properties = {
mode: {type: String},
data: {attribute: false},
};
constructor() {
super();
this.data = {};
}
}
空のオプションオブジェクトは、すべてのオプションのデフォルト値を指定することと同じです。
プロパティ宣言時のクラスフィールドに関する問題の回避
“プロパティ宣言時のクラスフィールドに関する問題の回避”へのパーマリンククラスフィールドは、リアクティブプロパティと問題のある相互作用があります。クラスフィールドは要素インスタンス上に定義されますが、リアクティブプロパティは要素プロトタイプ上にアクセサとして定義されます。JavaScriptのルールによると、インスタンスプロパティはプロトタイププロパティよりも優先され、事実上隠蔽します。これは、クラスフィールドが使用されている場合、リアクティブプロパティアクセサが機能せず、プロパティの設定が要素の更新をトリガーしないことを意味します。
class MyElement extends LitElement {
static properties = {foo: {type: String}}
foo = 'Default'; // ❌ this will make `foo` not reactive
}
JavaScriptでは、リアクティブプロパティを宣言する際にクラスフィールドを使用しないでください。代わりに、プロパティは要素コンストラクタで初期化する必要があります。
class MyElement extends LitElement {
static properties = {foo: {type: String}}
constructor() {
super();
this.foo = 'Default';
}
}
あるいは、リアクティブプロパティを宣言するためにBabelを使用した標準デコレータを使用することもできます。
class MyElement extends LitElement {
@property()
accessor foo = 'Default';
}
TypeScriptの場合、これらのパターンのいずれかを使用する限り、リアクティブプロパティの宣言にクラスフィールドを使用できます。
useDefineForClassFields
コンパイラオプションをfalse
に設定します。TypeScriptでデコレータを使用する場合は、既にこれが推奨されています。
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true, // If using decorators
"useDefineForClassFields": false,
}
}
class MyElement extends LitElement {
static properties = {foo: {type: String}}
foo = 'Default';
@property()
bar = 'Default';
}
- フィールドに
declare
キーワードを追加し、フィールドのイニシャライザをコンストラクタに入れます。
class MyElement extends LitElement {
declare foo: string;
static properties = {foo: {type: String}}
constructor() {
super();
this.foo = 'Default';
}
}
- 自動アクセサを使用するために、フィールドに
accessor
キーワードを追加します。
class MyElement extends LitElement {
static properties = {foo: {type: String}}
accessor foo = 'Default';
@property()
accessor bar = 'Default';
}
プロパティオプション
“プロパティオプション”へのパーマリンクオプションオブジェクトには、次のプロパティを含めることができます。
attribute
プロパティが属性に関連付けられているかどうか、または関連付けられた属性のカスタム名。デフォルト:true。
attribute
がfalseの場合、converter
、reflect
、type
オプションは無視されます。詳細は属性名の設定を参照してください。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のランタイムによって文字列のシリアル化/デシリアル化に使用され、型チェックメカニズムと混同しないでください。- プロパティのセッターが呼び出されます。
- セッターはコンポーネントの
requestUpdate
メソッドを呼び出します。 - プロパティの古い値と新しい値が比較されます。
- デフォルトでは、Litは値が変更されたかどうかを判定するために厳密な不等式テスト(つまり、
newValue !== oldValue
)を使用します。 - プロパティに
hasChanged
関数が存在する場合、その関数はプロパティの古い値と新しい値を引数として呼び出されます。
- デフォルトでは、Litは値が変更されたかどうかを判定するために厳密な不等式テスト(つまり、
- プロパティの変更が検出されると、非同期的に更新がスケジュールされます。既に更新がスケジュールされている場合は、更新は1回だけ実行されます。
- コンポーネントの
update
メソッドが呼び出され、変更されたプロパティが属性に反映され、コンポーネントのテンプレートが再レンダリングされます。 不変データパターン。オブジェクトと配列を不変として扱います。たとえば、
myArray
からアイテムを削除するには、新しい配列を作成します。this.myArray = this.myArray.filter((_, i) => i !== indexToRemove);
この例は単純ですが、不変データを管理するためにImmerのようなライブラリを使用すると便利なことがよくあります。これにより、深くネストされたオブジェクトを設定する場合の複雑なボイラープレートコードを回避できます。
更新を手動でトリガーする。データを変更し、
requestUpdate()
を呼び出して更新を直接トリガーします。例:this.myArray.splice(indexToRemove, 1);
this.requestUpdate();
引数を指定せずに呼び出された場合、
requestUpdate()
はhasChanged()
関数を呼び出さずに更新をスケジュールします。ただし、requestUpdate()
は現在のコンポーネントのみを更新することに注意してください。つまり、上記のコードを使用するコンポーネントが、this.myArray
をサブコンポーネントに渡す場合、サブコンポーネントは配列参照が変更されていないことを検出するため、更新されません。属性を監視する(属性からプロパティを設定する)には、属性値を文字列からプロパティ型に一致するように変換する必要があります。
属性を反映する(プロパティから属性を設定する)には、プロパティ値を文字列に変換する必要があります。
プロパティ名を変更してfalseをデフォルトにします。たとえば、Webプラットフォームでは
enabled
ではなくdisabled
属性(falseをデフォルト)を使用します。代わりに文字列値または数値値の属性を使用します。
オプションオブジェクトを省略するか、空のオプションオブジェクトを指定することは、すべてのオプションのデフォルト値を指定することと同じです。
内部リアクティブ状態
“内部リアクティブ状態”へのパーマリンク内部リアクティブ状態とは、コンポーネントの公開APIの一部ではないリアクティブプロパティを指します。これらの状態プロパティには対応する属性がなく、コンポーネントの外から使用することを意図していません。内部リアクティブ状態は、コンポーネント自体によって設定する必要があります。
@state
デコレータを使用して、内部リアクティブ状態を宣言します。
@state()
protected _active = false;
静的properties
クラスフィールドを使用して、state: true
オプションを使用して内部リアクティブ状態を宣言できます。
static properties = {
_active: {state: true}
};
constructor() {
this._active = false;
}
内部リアクティブ状態は、コンポーネントの外から参照しないでください。TypeScriptでは、これらのプロパティはprivateまたはprotectedとしてマークする必要があります。JavaScriptユーザー向けには、先頭のアンダースコア(_
)などの規則を使用して、privateまたはprotectedプロパティを識別することをお勧めします。
内部リアクティブ状態は、プロパティに関連付けられた属性がない点を除いて、公開リアクティブプロパティと同じように機能します。内部リアクティブ状態に対して指定できるオプションは、hasChanged
関数のみです。
@state
デコレータは、コードミニファイアに対して、プロパティ名をミニファイ中に変更できるというヒントとしても機能します。
プロパティ変更時の動作
“プロパティ変更時の動作”へのパーマリンクプロパティの変更は、リアクティブ更新サイクルをトリガーし、コンポーネントがテンプレートを再レンダリングします。
プロパティが変更されると、次のシーケンスが発生します。
オブジェクトまたは配列のプロパティを直接変更しても、オブジェクト自体が変わっていないため、更新はトリガーされません。詳細については、オブジェクトと配列のプロパティの変更を参照してください。
リアクティブな更新サイクルにフックして変更するには、多くの方法があります。詳細については、リアクティブ更新サイクルを参照してください。
プロパティ変更検出の詳細については、変更検出のカスタマイズを参照してください。
オブジェクトおよび配列プロパティの変更
「オブジェクトと配列のプロパティの変更」へのパーマリンクオブジェクトまたは配列を変更しても、オブジェクト参照は変わらないため、更新はトリガーされません。オブジェクトと配列のプロパティは、次の2つの方法のいずれかで処理できます。
一般的に、不変オブジェクトを使用したトップダウンのデータフローは、ほとんどのアプリケーションで最適です。これにより、新しい値をレンダリングする必要があるすべてのコンポーネントが確実にレンダリングされ(そして、データツリーの変更されていない部分は、それに依存するコンポーネントの更新を引き起こさないため、可能な限り効率的に実行されます)。
データを直接変更してrequestUpdate()
を呼び出すことは、高度なユースケースと見なすべきです。この場合、(または他のシステムが)変更されたデータを使用するすべてのコンポーネントを特定し、各コンポーネントでrequestUpdate()
を呼び出す必要があります。これらのコンポーネントがアプリケーション全体に分散されている場合、これは管理が困難になります。これをしっかりと行わないと、アプリケーションの2つの場所でレンダリングされるオブジェクトを変更しても、一方だけが更新される可能性があります。
特定のデータが単一のコンポーネントでのみ使用されることがわかっている単純なケースでは、必要であればデータを変更してrequestUpdate()
を呼び出す方が安全です。
プロパティはJavaScriptデータを入力として受信するのに適していますが、属性はHTMLがマークアップから要素を構成することを許可する標準的な方法であり、プロパティを設定するためにJavaScriptを使用する必要はありません。リアクティブプロパティに対してプロパティと属性の両方のインターフェースを提供することは、クライアントサイドのテンプレートエンジンを使用せずにレンダリングされるもの(CMSから提供される静的HTMLページなど)を含む、幅広い環境でLitコンポーネントが役立つ重要な方法です。
デフォルトでは、Litは各公開リアクティブプロパティに対応する監視対象属性を設定し、属性が変更されるとプロパティを更新します。プロパティ値は、必要に応じて反映(属性に書き戻す)することもできます。
要素のプロパティは任意の型にすることができますが、属性は常に文字列です。これは、文字列以外のプロパティの監視対象属性と反映属性に影響します。
属性を公開するブールプロパティは、falseをデフォルトにする必要があります。詳細については、ブール属性を参照してください。
属性名の設定
「属性名の設定」へのパーマリンクデフォルトでは、Litはすべての公開リアクティブプロパティに対応する監視対象属性を作成します。監視対象属性の名前は、小文字にしたプロパティ名です。
// observed attribute name is "myvalue"
@property({ type: Number })
myValue = 0;
// observed attribute name is "myvalue"
static properties = {
myValue: { type: Number },
};
constructor() {
super();
this.myValue = 0;
}
異なる名前の監視対象属性を作成するには、attribute
を文字列に設定します。
// Observed attribute will be called my-name
@property({ attribute: 'my-name' })
myName = 'Ogden';
// Observed attribute will be called my-name
static properties = {
myName: { attribute: 'my-name' },
};
constructor() {
super();
this.myName = 'Ogden'
}
プロパティに対して監視対象属性の作成を防止するには、attribute
をfalse
に設定します。プロパティはマークアップの属性から初期化されず、属性の変更はそのプロパティに影響しません。
// No observed attribute for this property
@property({ attribute: false })
myData = {};
// No observed attribute for this property
static properties = {
myData: { attribute: false },
};
constructor() {
super();
this.myData = {};
}
内部のリアクティブ状態には、関連付けられた属性がありません。
監視対象属性を使用して、マークアップからプロパティの初期値を提供できます。例:
<my-element myvalue="99"></my-element>
デフォルトコンバータの使用
「デフォルトコンバーターの使用」へのパーマリンクLitには、String
、Number
、Boolean
、Array
、Object
プロパティ型を処理するデフォルトコンバーターがあります。
デフォルトコンバーターを使用するには、プロパティ宣言でtype
オプションを指定します。
// Use the default converter
@property({ type: Number })
count = 0;
// Use the default converter
static properties = {
count: { type: Number },
};
constructor() {
super();
this.count = 0;
}
プロパティに型またはカスタムコンバーターを指定しない場合、type: String
を指定した場合と同じように動作します。
以下の表は、デフォルトコンバーターが各型について変換をどのように処理するかを示しています。
属性からプロパティへ
型 | 変換 |
---|---|
String | 対応する属性が要素に存在する場合は、プロパティを属性値に設定します。 |
Number | 対応する属性が要素に存在する場合は、プロパティをNumber(attributeValue) に設定します。 |
Boolean | 対応する属性が要素に存在する場合は、プロパティをtrueに設定します。 存在しない場合は、プロパティをfalseに設定します。 |
Object 、Array | 対応する属性が要素に存在する場合は、プロパティ値をJSON.parse(attributeValue) に設定します。 |
Boolean
を除くすべてのケースで、対応する属性が要素に存在しない場合、プロパティはデフォルト値を保持するか、デフォルトが設定されていない場合はundefined
になります。
プロパティから属性へ
型 | 変換 |
---|---|
String 、Number | プロパティが定義されていてnull以外の場合、属性をプロパティ値に設定します。 プロパティがnullまたはundefinedの場合、属性を削除します。 |
Boolean | プロパティがtruthyの場合、属性を作成し、その値を空文字列に設定します。 プロパティがfalsyの場合、属性を削除します。 |
Object 、Array | プロパティが定義されていてnull以外の場合、属性をJSON.stringify(propertyValue) に設定します。プロパティがnullまたはundefinedの場合、属性を削除します。 |
カスタムコンバータの提供
「カスタムコンバーターの提供」へのパーマリンクプロパティ宣言でconverter
オプションを使用して、カスタムプロパティコンバーターを指定できます。
myProp: {
converter: // Custom property converter
}
converter
はオブジェクトまたは関数にすることができます。オブジェクトの場合、fromAttribute
とtoAttribute
のキーを持つことができます。
prop1: {
converter: {
fromAttribute: (value, type) => {
// `value` is a string
// Convert it to a value of type `type` and return it
},
toAttribute: (value, type) => {
// `value` is of type `type`
// Convert it to a string and return it
}
}
}
converter
が関数の場合は、fromAttribute
の代わりに使用されます。
myProp: {
converter: (value, type) => {
// `value` is a string
// Convert it to a value of type `type` and return it
}
}
反映属性に対してtoAttribute
関数が提供されていない場合、属性はデフォルトコンバーターを使用してプロパティ値に設定されます。
toAttribute
がnull
またはundefined
を返す場合、属性は削除されます。
ブール属性
「ブール属性」へのパーマリンクブールプロパティを属性から構成可能にするには、**falseをデフォルトにする必要があります。**trueをデフォルトにすると、属性の有無にかかわらずtrueと等しいため、マークアップからfalseに設定できません。これは、Webプラットフォームの属性の標準的な動作です。
この動作がユースケースに合わない場合は、いくつかの選択肢があります。
属性反映の有効化
「属性反映の有効化」へのパーマリンクプロパティを変更するたびに、その値が対応する属性に反映されるようにプロパティを構成できます。反映属性は、属性がCSSと、querySelector
などのDOM APIから見えるため便利です。
例:
// Value of property "active" will reflect to attribute "active"
active: {reflect: true}
プロパティが変更されると、Litはデフォルトコンバーターの使用またはカスタムコンバーターの提供で説明されているように、対応する属性値を設定します。
属性は一般的に、要素自体ではなく所有者からの要素への入力と見なされるため、プロパティを属性に反映する操作は控えめにすべきです。現在、スタイリングやアクセシビリティなどのケースで必要ですが、:state
擬似セレクターやアクセシビリティオブジェクトモデルなど、プラットフォームがこれらのギャップを埋める機能を追加するにつれて、これは変わる可能性があります。
オブジェクトまたは配列型のプロパティの反映はお勧めしません。これにより、大きなオブジェクトがDOMにシリアル化され、パフォーマンスが低下する可能性があります。
Litは更新中に反映状態を追跡します。プロパティの変更が属性に反映され、属性の変更がプロパティを更新する場合、無限ループが発生する可能性があることに気づかれたかもしれません。ただし、Litはプロパティと属性が具体的に設定された時点を追跡して、これが発生しないようにします。
カスタムプロパティアクセサ
「カスタムプロパティアクセサ」へのパーマリンクデフォルトでは、LitElementはすべてのリアクティブプロパティに対してゲッター/セッターペアを生成します。プロパティを設定するたびに、セッターが呼び出されます。
// Declare a property
@property()
greeting: string = 'Hello';
...
// Later, set the property
this.greeting = 'Hola'; // invokes greeting's generated property accessor
// Declare a property
static properties = {
greeting: {},
}
constructor() {
this.super();
this.greeting = 'Hello';
}
...
// Later, set the property
this.greeting = 'Hola'; // invokes greeting's generated property accessor
生成されたアクセサは、自動的にrequestUpdate()
を呼び出し、更新がまだ開始されていない場合は更新を開始します。
カスタムプロパティアクセサの作成
「カスタムプロパティアクセサの作成」へのパーマリンクプロパティの取得と設定の方法を指定するには、独自のゲッター/セッターペアを定義できます。例:
private _prop = 0;
@property()
set prop(val: number) {
this._prop = Math.floor(val);
}
get prop() { return this._prop; }
static properties = {
prop: {},
};
_prop = 0;
set prop(val) {
this._prop = Math.floor(val);
}
get prop() { return this._prop; }
@property
または@state
デコレータでカスタムプロパティアクセサを使用するには、上記の例のように、セッターにデコレータを配置します。@property
または@state
デコレータ付きセッターはrequestUpdate()
を呼び出します。
ほとんどの場合、カスタムプロパティアクセサを作成する必要はありません。既存のプロパティから値を計算するには、willUpdate
コールバックを使用することをお勧めします。これにより、追加の更新をトリガーせずに更新サイクル中に値を設定できます。要素の更新後にカスタムアクションを実行するには、updated
コールバックを使用することをお勧めします。カスタムセッターは、ユーザーが設定する値を同期的に検証することが重要なまれなケースで使用できます。
クラスがプロパティに対して独自のアクセサを定義する場合、Litは生成されたアクセサでそれらを上書きしません。クラスがプロパティに対してアクセサを定義しない場合、スーパークラスがプロパティまたはアクセサを定義していても、Litはそれらを生成します。
Litによるプロパティアクセサの生成の防止
「Litによるプロパティアクセサの生成を防止する」へのパーマリンクまれに、サブクラスはスーパークラスに存在するプロパティのプロパティオプションを変更または追加する必要があります。
スーパークラスで定義されたアクセサを上書きするプロパティアクセサの生成をLitに防止するには、プロパティ宣言でnoAccessor
をtrue
に設定します。
static properties = {
myProp: { type: Number, noAccessor: true }
};
独自のアクセサを定義する場合は、noAccessor
を設定する必要はありません。
変更検出のカスタマイズ
「変更検出のカスタマイズ」へのパーマリンクすべてのリアクティブプロパティには、プロパティが設定されたときに呼び出される関数hasChanged()
があります。
hasChanged
はプロパティの旧値と新値を比較し、プロパティが変更されたかどうかを評価します。hasChanged()
がtrueを返す場合、Litは、まだスケジュールされていない場合、要素の更新を開始します。更新の詳細については、リアクティブ更新サイクルを参照してください。
hasChanged()
のデフォルトの実装では、厳密な不等号比較を使用します。つまり、newVal !== oldVal
の場合、hasChanged()
はtrue
を返します。
プロパティのhasChanged()
をカスタマイズするには、プロパティオプションとして指定します。
@property({
hasChanged(newVal: string, oldVal: string) {
return newVal?.toLowerCase() !== oldVal?.toLowerCase();
}
})
myProp: string | undefined;
static properties = {
myProp: {
hasChanged(newVal, oldVal) {
return newVal?.toLowerCase() !== oldVal?.toLowerCase();
}
}
};
次の例では、hasChanged()
は奇数値の場合のみtrueを返します。