はじめに
LocalStoreはArkTSが提供する、ページレベルの状態変数を保持するためのメモリ内「データベース」です。アプリケーションは複数のLocalStoreインスタンスを作成でき、UIAbilityインスタンス内で複数のページ間で状態を共有できます。ページ内でも共有可能であり、GetSharedインターフェースを通じて異なるページ間での共有も可能です。コンポーネントツリーのルートノード(@Entryデコレータ付きの@Component)にLocalStoreインスタンスを割り当てると、そのすべての子コンポーネントが自動的にアクセス権限を得ます。LocalStore内のプロパティはすべて変更可能であり、そのライフサイクルはアプリケーションによって管理されます。
制限事項
- パラメータ型要件:@LocalStorePropおよび@LocalStoreLinkのパラメータは文字列型である必要があります。そうでない場合、コンパイル時にエラーが発生します。例:
let store = new LocalStore();
store.setOrCreate('PropA', 48);
// エラー:コンパイルエラー
@LocalStoreProp() localStoreProp: number = 1;
@LocalStoreLink() localStoreLink: number = 2;
// 正しい記述
@LocalStoreProp('PropA') localStoreProp: number = 1;
@LocalStoreLink('PropA') localStoreLink: number = 2;
- Function型のサポート不可:@StorePropと@StoreLinkはFunction型の変数をデコレートできません。フレームワークは実行時エラーをスローします。
- プロパティ型の変更不可:LocalStoreが作成された後、名前付きプロパティの型は変更できません。後続のset呼び出しでは同じ型の値を使用する必要があります。
- ページレベルストレージの制限:getSharedインターフェースは現在のStageからwindowStage.loadContentで渡されたLocalStoreインスタンスのみを取得でき、それ以外の場合はundefinedを返します。
@LocalStorePropデコレータ
(1)デコレータの使用ルール
- パラメータ要件:
- keyは定数文字列であり、必須で引用符で囲む必要があります。
- デコレート可能な変数型にはObject、class、string、number、boolean、enumおよびその配列が含まれます。API12以降ではMap、Set、Date型もサポートされ、anyはサポートされていません。API12以降ではundefinedとnullもサポートされますが、明示的に型を指定することを推奨します。例:
@LocalStoreProp("AA") a: number | null = null; // 推奨
@LocalStoreProp("AA") a: number = null; // 非推奨
- 同期タイプ:LocalStoreのkeyに対応するプロパティとの単方向データ同期を確立します。LocalStoreからコンポーネント状態変数への同期のみが可能です。つまり、ArkUIフレームワークは@LocalStoreProp(key)のローカル値を変更できますが、ローカル値の変更はLocalStoreに戻りません。一方、LocalStoreのkeyに対応するプロパティが変更されると、@LocalStoreProp(key)に同期され、ローカル値が上書きされます。
- 初期値要件:必須です。LocalStoreインスタンスにプロパティが存在しない場合、この初期値で初期化してLocalStoreに保存されます。
(2)変数の伝達/アクセスルール
- 親ノードからの初期化・更新禁止:LocalStoreのkeyに対応するプロパティから初期化される必要があります。対応するkeyがない場合はローカルのデフォルト値が使用されます。
- 子ノードの初期化サポート:@State、@Link、@Prop、@Provideの初期化に使用できます。
- コンポーネント外からのアクセス不可。
(3)変更監視と動作表現
- 変更監視タイプ:
- boolean、string、number型は値の変更を監視できます。
- classまたはObject型はオブジェクト全体の代入とプロパティ変更を監視できます。
- array型は要素の追加、削除、更新を監視できます。
- Date型は全体の代入と関連インターフェースによるプロパティ更新を監視できます。
- Map型は全体の代入とインターフェースによる値更新を監視できます。
- Set型は全体の代入とインターフェースによる値更新を監視できます。
- フレームワーク動作:
- コンポーネント内の変数値の変更はLocalStoreに書き戻されません。
- 変数の変更により関連コンポーネントが再描画されます。
- LocalStoreの値の変更はローカルの変更を上書きします。
@LocalStoreLinkデコレータ
(1)デコレータの使用ルール
- パラメータ要件:@LocalStorePropと同じです。keyは定数文字列で、必須で引用符で囲み、変数型の要件も同じです。
- 同期タイプ:LocalStoreのkeyに対応するプロパティとの双方向データ同期を確立します。つまり、ローカルでの変更はLocalStoreに書き戻され、LocalStoreの変更もバインドされたプロパティに同期されます(単方向および双方向バインド変数を含む)。
- 初期値要件:必須です。LocalStoreインスタンスにプロパティが存在しない場合、この初期値で初期化してLocalStoreに保存されます。
(2)変数の伝達/アクセスルール
- 親ノードからの初期化・更新禁止:@LocalStorePropと同じです。
- 子ノードの初期化サポート:@LocalStorePropと同じです。
- コンポーネント外からのアクセス不可。
(3)変更監視と動作表現
- 変更監視タイプ:@LocalStorePropと同じです。
- フレームワーク動作:
- コンポーネント内の値の変更はLocalStoreに同期されます。
- LocalStoreの値の変更により、バインドされたデータ(単方向および双方向)が同期変更されます。
- デコレートされたデータ自体が状態変数の場合、変更により所属するカスタムコンポーネントが再レンダリングされます。
利用シナリオ
(1)アプリケーションロジックにおけるLocalStoreの使用
LocalStoreインスタンスを作成し、get、link、propなどのインターフェースを使用してプロパティを操作します。例:
let para: Record<string,number> = { 'PropA': 47 };
let store: LocalStore = new LocalStore( para);
let propA: number | undefined = store.get('PropA');
let link1: SubscribedAbstractProperty<number> = store.link('PropA');
let link2: SubscribedAbstractProperty<number> = store.link('PropA');
let prop: SubscribedAbstractProperty<number> = store.prop('PropA');
link1.set(48);
prop.set(1);
link1.set(49);
(2)UI内部からのLocalStoreの使用
@LocalStorePropおよび@LocalStoreLinkを使用してUIコンポーネント内で状態変数を取得します。例:
class PropB {
code: number;
constructor(code: number) {
this.code = code;
}
}
let para: Record<string, number> = { 'PropA': 47 };
let store: LocalStore = new LocalStore( para);
store.setOrCreate('PropB', new PropB(50));
@Component
struct Child {
@LocalStoreLink('PropA') childLinkNumber: number = 1;
@LocalStoreLink('PropB') childLinkObject: PropB = new PropB(0);
build() {
Column() {
Button(`Child from LocalStore ${this.childLinkNumber}`).onClick(() => {this.childLinkNumber += 1;})
Button(`Child from LocalStore ${this.childLinkObject.code}`).onClick(() => {this.childLinkObject.code += 1;})
}
}
}
@Entry(store)
@Component
struct CompA {
@LocalStoreLink('PropA') parentLinkNumber: number = 1;
@LocalStoreLink('PropB') parentLinkObject: PropB = new PropB(0);
build() {
Column({ space: 15 }) {
Button(`Parent from LocalStore ${this.parentLinkNumber}`).onClick(() => {this.parentLinkNumber += 1;})
Button(`Parent from LocalStore ${this.parentLinkObject.code}`).onClick(() => {this.parentLinkObject.code += 1;})
Child()
}
}
}
(3)@LocalStorePropとLocalStoreの単方向同期シナリオ
CompAコンポーネントとChildコンポーネントでstorageの'PropA'との単方向同期データを作成します。例:
let para: Record<string, number> = { 'PropA': 47 };
let store: LocalStore = new LocalStore( para);
@Entry(store)
@Component
struct CompA {
@LocalStoreProp('PropA') storageProp1: number = 1;
build() {
Column({ space: 15 }) {
Button(`Parent from LocalStore ${this.storageProp1}`).onClick(() => {this.storageProp1 += 1})
Child()
}
}
}
@Component
struct Child {
@LocalStoreProp('PropA') storageProp2: number = 2;
build() {
Column({ space: 15 }) {
Text(`Parent from LocalStore ${this.storageProp2}`)
}
}
}
(4)@LocalStoreLinkとLocalStoreの双方向同期シナリオ
例:
let para: Record<string, number> = { 'PropA': 47 };
let store: LocalStore = new LocalStore( para);
let linkToPropA: SubscribedAbstractProperty<object> = store.link('PropA');
@Entry(store)
@Component
struct CompA {
@LocalStoreLink('PropA') storageLink: number = 1;
build() {
Column() {
Text(`incr @LocalStoreLink variable`).onClick(() => {this.storageLink += 1})
Text(`@LocalStoreLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`)
}
}
}
(5)兄弟コンポーネント間での状態変数の同期
@LocalStoreLinkを使って兄弟コンポーネントの状態を同期します。例:
let ls: Record<string, number> = { 'countStorage': 1 }
let store: LocalStore = new LocalStore(ls);
@Component
struct Child {
label: string = 'no name';
@LocalStoreLink('countStorage') playCountLink: number = 0;
build() {
Row() {
Text(this.label).width(50).height(60).fontSize(12)
Text(`playCountLink ${this.playCountLink}: inc by 1`).onClick(() => {this.playCountLink += 1;})
}.width(300).height(60)
}
}
@Entry(store)
@Component
struct Parent {
@LocalStoreLink('countStorage') playCount: number = 0;
build() {
Column() {
Row() {
Text('Parent').width(50).height(60).fontSize(12)
Text(`playCount ${this.playCount} dec by 1`).onClick(() => {this.playCount -= 1;})
}.width(300).height(60)
Row() {
Text('LocalStore').width(50).height(60).fontSize(12)
Text(`countStorage ${this.playCount} incr by 1`).onClick(() => {store.set<number | undefined>('countStorage', Number(store.get<number>('countStorage')) + 1);})
}.width(300).height(60)
Child({ label: 'ChildA' })
Child({ label: 'ChildB' })
Text(`playCount in LocalStore for debug ${store.get<number>('countStorage')}`).width(300).height(60).fontSize(12)
}
}
}
(6)UIAbilityから複数のビューへLocalStoreインスタンスを共有
UIAbility内でLocalStoreインスタンスを作成し、windowStage.loadContentで共有します。ページではgetSharedで取得します。例:
- EntryAbility.ets:
import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
para:Record<string, number> = { 'PropA': 47 };
store: LocalStore = new LocalStore(this.para);
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.store);
}
}
- index.ets:
import { router } from '@kit.ArkUI';
let store = LocalStore.getShared()
@Entry(store)
@Component
struct Index {
@LocalStoreLink('PropA') propA: number = 1;
build() {
Row() {
Column() {
Text(`${this.propA}`).fontSize(50).fontWeight(FontWeight.Bold)
Button("To Page").onClick(() => {
this.getUIContext().getRouter().pushUrl({
url: 'pages/Page'
})
})
}.width('100%')
}.height('100%')
}
}
- Page.ets:
import { router } from '@kit.ArkUI';
let store = LocalStore.getShared()
@Entry(store)
@Component
struct Page {
@LocalStoreLink('PropA') propA: number = 2;
build() {
Row() {
Column() {
Text(`${this.propA}`).fontSize(50).fontWeight(FontWeight.Bold)
Button("Change propA").onClick(() => {this.propA = 100;})
Button("Back Index").onClick(() => {this.getUIContext().getRouter().back()})
}.width('100%')
}
}
}
(7)カスタムコンポーネントへのLocalStoreインスタンスの受け渡し
- 属性定義時の受け取り:
- インスタンスは2番目の引数として渡す必要があり、それ以外の場合はエラーになります。
- 例:
let localStore1: LocalStore = new LocalStore();
localStore1.setOrCreate('PropA', 'PropA');
let localStore2: LocalStore = new LocalStore();
localStore2.setOrCreate('PropB', 'PropB');
@Entry(localStore1)
@Component
struct Index {
@LocalStoreLink('PropA') PropA: string = 'Hello World';
@State count: number = 0;
build() {
Row() {
Column() {
Text(this.PropA).fontSize(50).fontWeight(FontWeight.Bold)
Child({ count: this.count }, localStore2)
}.width('100%')
}.height('100%')
}
}
@Component
struct Child {
@Link count: number;
@LocalStoreLink('PropB') PropB: string = 'Hello World';
build() {
Text(this.PropB).fontSize(50).fontWeight(FontWeight.Bold)
}
}
- 属性未定義時の受け取り:LocalStoreインスタンスを1つだけ引数として渡すことができます。例:
let localStore1: LocalStore = new LocalStore();
localStore1.setOrCreate('PropA', 'PropA');
let localStore2: LocalStore = new LocalStore();
localStore2.setOrCreate('PropB', 'PropB');
@Entry(localStore1)
@Component
struct Index {
@LocalStoreLink('PropA') PropA: string = 'Hello World';
@State count: number = 0;
build() {
Row() {
Column() {
Text(this.PropA).fontSize(50).fontWeight(FontWeight.Bold)
Child(localStore2)
}.width('100%')
}.height('100%')
}
}
@Component
struct Child {
build() {
Text("hello").fontSize(50).fontWeight(FontWeight.Bold)
}
}
- 親コンポーネントからの初期化不要な属性の受け取り:最初の引数に{}を渡します。例:
let localStore1: LocalStore = new LocalStore();
localStore1.setOrCreate('PropA', 'PropA');
let localStore2: LocalStore = new LocalStore();
localStore2.setOrCreate('PropB', 'PropB');
@Entry(localStore1)
@Component
struct Index {
@LocalStoreLink('PropA') PropA: string = 'Hello World';
@State count: number = 0;
build() {
Row() {
Column() {
Text(this.PropA).fontSize(50).fontWeight(FontWeight.Bold)
Child({}, localStore2)
}.width('100%')
}.height('100%')
}
}
@Component
struct Child {
@State count: number = 5;
@LocalStoreLink('PropB') PropB: string = 'Hello World';
build() {
Text(this.PropB).fontSize(50).fontWeight(FontWeight.Bold)
}
}
(8)NavigationコンポーネントとLocalStoreの連携
異なるLocalStoreインスタンスをカスタムコンポーネントに渡し、ナビゲーション遷移時にそれぞれのバインド値を表示します。例:
let localStoreA: LocalStore = new LocalStore();
localStoreA.setOrCreate('PropA', 'PropA');
let localStoreB: LocalStore = new LocalStore();
localStoreB.setOrCreate('PropB', 'PropB');
let localStoreC: LocalStore = new LocalStore();
localStoreC.setOrCreate('PropC', 'PropC');
@Entry
@Component
struct MyNavigationTestStack {
@Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
@Builder
PageMap(name: string) {
if (name === 'pageOne') {
pageOneStack({}, localStoreA)
} else if (name === 'pageTwo') {
pageTwoStack({}, localStoreB)
} else if (name === 'pageThree') {
pageThreeStack({}, localStoreC)
}
}
build() {
Column({ space: 5 }) {
Navigation(this.pageInfo) {
Column() {
Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }).width('80%').height(40).margin(20).onClick(() => {
this.pageInfo.pushPath({ name: 'pageOne' });
})
}
}.title('NavIndex').navDestination(this.PageMap).mode(NavigationMode.Stack).borderWidth(1)
}
}
}
@Component
struct pageOneStack {
@Consume('pageInfo') pageInfo: NavPathStack;
@LocalStoreLink('PropA') PropA: string = 'Hello World';
build() {
NavDestination() {
Column() {
NavigationContentMsgStack()
Text(`${this.PropA}`)
Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }).width('80%').height(40).margin(20).onClick(() => {
this.pageInfo.pushPathByName('pageTwo', null);
})
}.width('100%').height('100%')
}.title('pageOne').onBackPressed(() => {this.pageInfo.pop(); return true;})
}
}
まとめ
HarmonyOS Nextにおいて、ページレベルストレージLocalStoreは、開発者がページレベルの状態変数を管理するための便利な方法を提供します。@LocalStorePropおよび@LocalStoreLinkデコレータを使用することで、単方向および双方向のデータ同期を実現し、さまざまなシナリオに対応できます。単一ページ内でも複数ページ間でも、LocalStoreは効果的に状態を共有できるため、アプリケーションの開発効率とユーザーエクスペリエンスを向上させます。同時に、LocalStoreの制限事項を理解し、状態変数の正しく使用・管理することが重要です。
拡張情報
- 実際のアプリケーションでは、他のHarmonyOS Nextの機能や特性(コンポーネント化開発、状態管理フレームワークなど)と組み合わせることで、アプリケーションのアーキテクチャとパフォーマンスをさらに最適化できます。
- 大規模アプリケーションでは、ReduxやVuexのようなより複雑な状態管理スキームを検討し、グローバル状態をより効果的に管理できます。
- LocalStoreを使用する際は、データのセキュリティとプライバシーに注意が必要です。機密情報を保存しないようにし、暗号化技術などを用いてデータ保護を行うことを推奨します。
- 新しい使い方やテクニックを積極的に探求・試行することで、開発効率とコード品質を高めることができます。たとえば、特定のモジュールの状態を管理するためにカスタムLocalStoreインスタンスを使用したり、アニメーション効果と組み合わせてユーザーエクスペリエンスを強化したりできます。