状態管理の概要
前の説明では、構築したページのほとんどは静的インターフェイスでした。動的でインタラクティブなインターフェイスを構築したい場合は、「状態」の概念を導入する必要があります。
図 1 の レンダリング
上の例では、ユーザーのアプリケーションとの対話によりテキスト状態の変化がトリガーされ、これにより UI がレンダリングされ、UI が「Hello World」から「Hello ArkUI」に変わります。
宣言型 UI プログラミング フレームワークでは、UI はプログラム状態の実行結果であり、ユーザーはアプリケーションの実行時状態がパラメーターである UI モデルを構築します。パラメータが変更されると、UI も返された結果として対応する変更を加えます。これらの実行時の状態変化によってもたらされる UI の再レンダリングは、ArkUI の状態管理メカニズムと総称されます。
カスタム コンポーネントには変数があり、状態変数になるには変数をデコレータで装飾する必要があります。状態変数が変更されると UI レンダリングが更新されます。状態変数を使用しない場合、UI は初期化中にのみレンダリングされ、後で更新されません。以下の図は、State と View (UI) の関係を示しています。
- ビュー (UI): UI レンダリングは、一般に、カスタム コンポーネントのビルド メソッドおよび @Builder によって装飾されたメソッド内の UI 記述を指します。
- 状態: 状態は通常、デコレーターによって装飾されたデータを指します。ユーザーは、コンポーネントのイベント メソッドをトリガーして状態データを変更します。状態データが変更されると、UI が再レンダリングされます。
基本的な考え方
- 状態変数: 状態デコレータによって装飾された変数。変更すると UI レンダリングが更新されます。
- 通常の変数: 状態のない変数。通常は補助計算に使用されます。その変更によって UI が更新されることはありません。
- データ ソース/同期ソース: さまざまな状態データに同期できる、状態変数の元のソース。これは通常、親コンポーネントから子コンポーネントに渡されるデータを意味します。
- 名前付きパラメータ メカニズム: 親コンポーネントは、指定されたパラメータを子コンポーネントの状態変数に渡します。これは、親と子の同期パラメータを渡す主な手段です。例: CompA: ({ aProp: this.aProp })。
- 親コンポーネントからの初期化: 親コンポーネントは、名前付きパラメーター メカニズムを使用して、指定されたパラメーターを子コンポーネントに渡します。親コンポーネントから値が渡された場合、ローカル初期化のデフォルト値は上書きされます。例:
@Component
struct MyComponent {
@State count: number = 0;
private increaseBy: number = 1;
build() {
}
}
@Component
struct Parent {
build() {
Column() {
// 从父组件初始化,覆盖本地定义的默认值
MyComponent({ count: 1, increaseBy: 2 })
}
}
}
- サブノードの初期化: コンポーネント内の状態変数をサブコンポーネントに渡して、サブコンポーネントに対応する状態変数を初期化できます。上記と同じ例です。
- ローカル初期化: 変数が宣言されると、初期化のデフォルト値として値が割り当てられます。例: @State カウント: 数値 = 0。
デコレータの概要
ArkUI にはさまざまなデコレータが用意されており、これらのデコレータを使用すると、状態変数はコンポーネント内の変更を観察できるだけでなく、親子コンポーネント、コンポーネント間のレベル、グローバルな変更など、異なるコンポーネント レベル間で受け渡すこともできます。状態変数の影響範囲に応じて、すべてのデコレーターは次のように大別できます。
- コンポーネントの状態を管理するデコレータ: コンポーネント レベルの状態管理では、コンポーネント内の変更および異なるコンポーネント レベルの変更を監視できますが、同じコンポーネント ツリー、つまり同じページのみを監視する必要があります。
- アプリケーションの状態を管理するデコレータ: アプリケーション レベルの状態管理。さまざまなページやさまざまな UIAbility の状態変化を監視でき、アプリケーション内のグローバルな状態管理です。
データ転送形式と同期タイプの観点から、デコレータは次のように分類することもできます。
- 読み取り専用の片道パス。
- 変更可能な双方向転送。
具体的なデコレータの紹介については、管理コンポーネントが持つ状態と管理アプリケーションが持つ状態を参照してください。開発者はこれらの機能を柔軟に活用して、データとUIの連携を実現できます。
上の図では、コンポーネント部分のデコレータがコンポーネントレベルの状態管理であり、アプリケーション部分がアプリケーションの状態管理です。開発者は @StorageLink/@LocalStorageLink と @StorageProp/@LocalStorageProp を使用して、アプリケーションとコンポーネントの状態の双方向および一方向の同期を実現できます。図の矢印の方向はデータの同期方向で、一重の矢印は一方向の同期、二重の矢印は双方向の同期です。
管理コンポーネントが所有する状態、つまり、図のコンポーネント レベルでの状態管理:
- @State: @State によって修飾された変数には、それが属するコンポーネントの状態が含まれており、そのサブコンポーネントの一方向および双方向同期のためのデータ ソースとして使用できます。その値が変更されると、関連するコンポーネントのレンダリングが更新されます。
- @Prop: @Prop で修飾された変数は、親コンポーネントとの一方向の同期関係を確立できます。@Prop で修飾された変数は変更可能ですが、変更は親コンポーネントに同期されません。
- @Link: @Link で修飾された変数と親コンポーネントの状態変数は双方向の同期関係を構築し、親コンポーネントは @Link で修飾された変数の変更と親コンポーネントの更新の同期を受け入れます。 @Link で修飾された変数にも同期されます。
- @Provide/@Consume: @Provide/@Consume で修飾された変数は、コンポーネント レベル (マルチレイヤー コンポーネント) 間で状態変数を同期するために使用され、パラメーター命名メカニズムを経由せずに、エイリアス (エイリアス) またはプロパティ名を通じてバインドできます。
- @Observed: @Observed はクラスを装飾し、多層のネストされたシーンを観察する必要があるクラスは @Observed で装飾する必要があります。@Observed を単独で使用しても効果はないため、@ObjectLink および @Prop と組み合わせて使用する必要があります。
- @ObjectLink: @ObjectLink で修飾された変数は、@Observed で修飾されたクラスのインスタンスを受け取ります。これは、マルチレイヤーのネストされたシーンを監視し、親コンポーネントのデータ ソースとの双方向同期を構築するために使用されます。
説明する
@Observed/@ObjectLink のみがネストされたシーンを観察でき、他の状態変数は最初のレイヤーのみを観察できます。詳細については、各デコレータの章の「変更と動作の観察」セクションを参照してください。
アプリケーションが所有する状態を管理します。つまり、図のアプリケーション レベルでの状態管理です。
- AppStorage は、アプリケーション内の特別なシングルトン LocalStorage オブジェクトです。これはアプリケーション レベルのデータベースであり、プロセスにバインドされています。@StorageProp および @StorageLink デコレータを通じてコンポーネントとリンクできます。
- AppStorage はアプリケーション状態の「ハブ」です。コンポーネント (UI) と対話する必要があるデータは、永続データ PersistentStorage や環境変数Environment など、AppStorage に保存されます。次に、UI は、AppStorage が提供するデコレータまたは API インターフェイスを介してこれらのデータにアクセスします。
- このフレームワークは LocalStorage も提供しており、AppStorage は LocalStorage の特別なシングルトンです。LocalStorage は、アプリケーションによって宣言されたアプリケーション状態のメモリ内の「データベース」です。通常、ページレベルの状態共有に使用されます。@LocalStorageProp および @LocalStorageLink デコレータは UI とリンクできます。
その他の状態管理機能
@Watch は、状態変数の変化を監視するために使用されます。
$$ 演算子: TS 変数と組み込みコンポーネントの内部状態が同期されるように、組み込みコンポーネントの TS 変数への参照を提供します。
コンポーネントが所有する状態を管理する
@State デコレータ: コンポーネント内の状態
@State で装飾された変数、つまり状態変数は、変数に状態プロパティが設定されると、カスタム コンポーネントのレンダリングにバインドされます。状態が変化すると、UI のレンダリングもそれに応じて変化します。
状態変数関連のデコレータの中で、@State は最も基本的なもので、変数に状態属性を持たせることができるデコレータであり、ほとんどの状態変数のデータ ソースでもあります。
概要
@State 装飾変数は、宣言パラダイムの他の装飾変数と同様、プライベートであり、コンポーネント内からのみアクセスでき、宣言時に型とローカル初期化を指定する必要があります。オプションで、名前付きパラメーター メカニズムを使用して、親コンポーネントから初期化を実行することもできます。
@State で修飾された変数には次の特性があります。
- 一方向または双方向のデータ同期は、@State 装飾変数と、サブコンポーネント内の @Prop、@Link、または @ObjectLink 装飾変数の間で確立されます。
- @State デコレーションの変数ライフ サイクルは、それが属するカスタム コンポーネントのライフ サイクルと同じです。
デコレータの使用ルールの説明
@State変数デコレータ |
説明する |
---|---|
デコレータパラメータ |
なし |
同期タイプ |
親コンポーネント内のいかなる種類の変数とも同期されません。 |
装飾が可能な変数型 |
オブジェクト、クラス、文字列、数値、ブール型、列挙型、およびこれらの型の配列。 タイプを指定する必要があります。 Any はサポートされず、単純型と複合型の共用体型はサポートされず、未定義と null は許可されません。 説明する Date 型を修飾しないことをお勧めします。修飾すると、アプリケーションが異常な動作をする可能性があります。 Length、ResourceStr、および ResourceColor 型はサポートされていません。Length、ResourceStr、および ResourceColor は、単純型と複合型の共用体型です。 |
修飾された変数の初期値 |
を指定する必要があります。 |
変数の転送・アクセスルールの説明
パス/アクセス |
説明する |
---|---|
親コンポーネントから初期化する |
オプションで、親コンポーネントからまたはローカルで初期化します。 親コンポーネントの通常の変数、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink、および @LocalStorageProp で装飾された変数をサポートし、子コンポーネントの @State を初期化します。 |
子コンポーネントの初期化に使用されます |
@State で装飾された変数は、サブコンポーネント、@State、@Link、@Prop、@Provide の通常の変数の初期化をサポートします。 |
コンポーネント外部へのアクセスをサポートするかどうか |
サポートされていません。コンポーネント内でのみアクセス可能です。 |
図 1 初期化ルールの図
変化や行動を観察する
状態変数に対するすべての変更が UI を更新するわけではありません。フレームワークによって監視できる変更のみが UI を更新します。このセクションでは、どのような変更が観察されるのか、また、変更が観察された後にフレームワークがどのように UI を更新するか、つまりフレームワークの動作がどのようなものであるかを紹介します。
変化を観察する
- 装飾データ型がブール型、文字列型、数値型の場合、値の変化を観察できます。
// for simple type
@State count: number = 0;
// value changing can be observed
this.count = 1;
- 装飾されたデータ型がクラスまたはオブジェクトの場合、それ自体の割り当ての変更とその属性割り当ての変更、つまり Object.keys(observedObject) によって返されるすべての属性を観察できます。例としては以下のようなものがあります。
ClassA クラスと Model クラスを宣言します。
class ClassA {
public value: string;
constructor(value: string) {
this.value = value;
}
}
class Model {
public value: string;
public name: ClassA;
constructor(value: string, a: ClassA) {
this.value = value;
this.name = a;
}
}
@State 装飾のタイプは Model です
// class类型
@State title: Model = new Model('Hello', new ClassA('World'));
@State 修飾された変数への代入。
// class类型赋值
this.title = new Model('Hi', new ClassA('ArkUI'));
@State 装飾された変数のプロパティに値を割り当てます。
// class属性的赋值
this.title.value = 'Hi'
ネストされたプロパティへの割り当ては観察されません。
// 嵌套的属性赋值观察不到
this.title.name.value = 'ArkUI'
- 装飾されたオブジェクトが配列の場合、配列自体の割り当てと、配列の追加、削除、更新の変更を観察できます。例としては以下のようなものがあります。
ClassA クラスと Model クラスを宣言します。
class Model {
public value: number;
constructor(value: number) {
this.value = value;
}
}
@Stateで装飾されたオブジェクトがModel型の配列の場合。
@State title: Model[] = [new Model(11), new Model(1)]
配列自体の割り当ては監視可能です。
this.title = [new Model(2)]
配列項目の割り当てを確認できます。
this.title[0] = new Model(2)
配列項目が削除されていることがわかります。
this.title.pop()
追加された配列項目が確認できます。
this.title.push(new Model(12))
フレームの動作
- 状態変数が変更されると、その状態変数に依存するコンポーネントをクエリします。
- 状態変数に依存するコンポーネントの update メソッドを実行すると、コンポーネントはレンダリングを更新します。
- この状態変数に関連しないコンポーネントまたは UI の説明は再レンダリングされないため、ページのレンダリングはオンデマンドで更新できます。
使用するシーン
単純型の変数の装飾
次の例は、単純なタイプの @State 装飾です。count は状態変数として @State によって装飾され、count の変更によりボタン コンポーネントが更新されます。
- 状態変数 count が変化すると、Button コンポーネントのみがそれに関連付けられていることがわかります。
- オンデマンド更新を実現するには、Buttonコンポーネントのupdateメソッドを実行します。
@Entry
@Component
struct MyComponent {
@State count: number = 0;
build() {
Button(`click times: ${this.count}`)
.onClick(() => {
this.count += 1;
})
}
}
クラスオブジェクト型の変数を装飾する
-
カスタム コンポーネント MyComponent は、@State によって装飾された状態変数 count と title を定義します。ここで、タイトルのタイプはカスタム クラス Model です。count または title の値が変更された場合は、MyComponent の状態変数を使用して UI コンポーネントをクエリし、再レンダリングします。
-
EntryComponent には複数の MyComponent コンポーネント インスタンスがあり、最初の MyComponent の内部状態が変更されても 2 番目の MyComponent には影響しません。
class Model {
public value: string;
constructor(value: string) {
this.value = value;
}
}
@Entry
@Component
struct EntryComponent {
build() {
Column() {
// 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化
MyComponent({ count: 1, increaseBy: 2 })
MyComponent({ title: new Model('Hello, World 2'), count: 7 })
}
}
}
@Component
struct MyComponent {
@State title: Model = new Model('Hello World');
@State count: number = 0;
private increaseBy: number = 1;
build() {
Column() {
Text(`${this.title.value}`)
Button(`Click to change title`).onClick(() => {
// @State变量的更新将触发上面的Text组件内容更新
this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI';
})
Button(`Click to increase count=${this.count}`).onClick(() => {
// @State变量的更新将触发该Button组件的内容更新
this.count += this.increaseBy;
})
}
}
}
この例から、@State 変数の最初のレンダリングの初期化プロセスを理解できます。
- デフォルトのローカル初期化を使用します。
@State title: Model = new Model('Hello World');
@State count: number = 0;
- @State の場合、名前付きパラメータ メカニズムによって渡される値は必須ではありません。名前付きパラメータが渡されない場合は、ローカル初期化のデフォルト値が使用されます。
MyComponent({ count: 1, increaseBy: 2 })