## 一、概述

LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内的“数据库”。应用程序可以创建多个LocalStorage实例,其支持UIAbility实例内多个页面间状态共享,可在页面内共享,也能通过GetShared接口跨页面共享。组件树的根节点(被@Entry装饰的@Component)可被分配LocalStorage实例,其所有子组件实例将自动获得访问权限。LocalStorage中的属性都是可变的,其生命周期由应用程序决定。


## 二、限制条件

1. **参数类型要求**:@LocalStorageProp和@LocalStorageLink的参数必须为string类型,否则编译期会报错。例如:

   ```typescript

   let storage = new LocalStorage();

   storage.setOrCreate('PropA', 48);

   // 错误写法,编译报错

   @LocalStorageProp() localStorageProp: number = 1;

   @LocalStorageLink() localStorageLink: number = 2;

   // 正确写法

   @LocalStorageProp('PropA') localStorageProp: number = 1;

   @LocalStorageLink('PropA') localStorageLink: number = 2;

   ```

2. **不支持Function类型变量**:@StorageProp与@StorageLink不支持装饰Function类型的变量,框架会抛出运行时错误。

3. **属性类型不可更改**:LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。

4. **页面级存储限制**:getShared接口仅能获取当前Stage通过windowStage.loadContent传入的LocalStorage实例,否则返回undefined。


## 三、@LocalStorageProp装饰器

### (一)装饰器使用规则

1. **参数要求**:

   - key为常量字符串,必填且需带引号。

   - 允许装饰的变量类型包括Object、class、string、number、boolean、enum类型及其数组,API12及以上支持Map、Set、Date类型等,不支持any,API12及以上支持undefined和null类型(建议显式指定类型)。例如:

   ```typescript

   @LocalStorageProp("AA") a: number | null = null; // 推荐

   @LocalStorageProp("AA") a: number = null; // 不推荐

   ```

2. **同步类型**:与LocalStorage中key对应的属性建立单向数据同步,从LocalStorage到组件状态变量。即ArkUI框架支持修改@LocalStorageProp(key)在本地的值,但本地值的修改不会同步回LocalStorage中;而LocalStorage中key对应的属性值发生改变时,会同步给@LocalStorageProp(key)并覆盖本地值。

3. **初始值要求**:必须指定,若LocalStorage实例中不存在属性,则用该初始值初始化并存入LocalStorage。


### (二)变量传递/访问规则

1. **禁止从父节点初始化和更新**:只能从LocalStorage中key对应的属性初始化,无对应key时用本地默认值。

2. **支持初始化子节点**:可用于初始化@State、@Link、@Prop、@Provide。

3. **不支持组件外访问**。


### (三)观察变化和行为表现

1. **观察变化类型**:

   - boolean、string、number类型可观察数值变化。

   - class或Object类型可观察对象整体赋值和属性变化。

   - array类型可观察数组添加、删除、更新单元变化。

   - Date类型可观察整体赋值及通过相关接口更新属性。

   - Map类型可观察整体赋值及通过接口更新值。

   - Set类型可观察整体赋值及通过接口更新值。

2. **框架行为**:

   - 组件内变量值变化不回写LocalStorage。

   - 变量变化使关联组件刷新。

   - LocalStorage中值变化会覆盖本地修改。


## 四、@LocalStorageLink装饰器

### (一)装饰器使用规则

1. **参数要求**:同@LocalStorageProp,key为常量字符串,必填且带引号,变量类型要求相同。

2. **同步类型**:与LocalStorage中key对应的属性建立双向数据同步,即本地修改会写回LocalStorage,LocalStorage中的修改也会同步到绑定的属性上(包括单向和双向绑定变量)。

3. **初始值要求**:必须指定,若LocalStorage实例中不存在属性,则用该初始值初始化并存入LocalStorage。


### (二)变量传递/访问规则

1. **禁止从父节点初始化和更新**:同@LocalStorageProp。

2. **支持初始化子节点**:同@LocalStorageProp。

3. **不支持组件外访问**。


### (三)观察变化和行为表现

1. **观察变化类型**:与@LocalStorageProp相同。

2. **框架行为**:

   - 组件内数值改变同步回LocalStorage。

   - LocalStorage中值改变,绑定的数据(包括双向和单向)同步修改。

   - 装饰的数据本身是状态变量时,改变会引起所属自定义组件重新渲染。


## 五、使用场景

### (一)应用逻辑使用LocalStorage

通过创建LocalStorage实例,使用get、link、prop等接口操作属性,如:

```typescript

let para: Record<string,number> = { 'PropA': 47 };

let storage: LocalStorage = new LocalStorage( para);

let propA: number | undefined = storage.get('PropA');

let link1: SubscribedAbstractProperty<number> = storage.link('PropA');

let link2: SubscribedAbstractProperty<number> = storage.link('PropA');

let prop: SubscribedAbstractProperty<number> = storage.prop('PropA');

link1.set(48);

prop.set(1);

link1.set(49);

```


### (二)从UI内部使用LocalStorage

借助@LocalStorageProp和@LocalStorageLink在UI组件内部获取状态变量,如:

```typescript

class PropB {

 code: number;

 constructor(code: number) {

   this.code = code;

 }

}

let para: Record<string, number> = { 'PropA': 47 };

let storage: LocalStorage = new LocalStorage( para);

storage.setOrCreate('PropB', new PropB(50));

@Component

struct Child {

 @LocalStorageLink('PropA') childLinkNumber: number = 1;

 @LocalStorageLink('PropB') childLinkObject: PropB = new PropB(0);

 build() {

   Column() {

     Button(`Child from LocalStorage ${this.childLinkNumber}`).onClick(() => {this.childLinkNumber += 1;})

     Button(`Child from LocalStorage ${this.childLinkObject.code}`).onClick(() => {this.childLinkObject.code += 1;})

   }

 }

}

@Entry(storage)

@Component

struct CompA {

 @LocalStorageLink('PropA') parentLinkNumber: number = 1;

 @LocalStorageLink('PropB') parentLinkObject: PropB = new PropB(0);

 build() {

   Column({ space: 15 }) {

     Button(`Parent from LocalStorage ${this.parentLinkNumber}`).onClick(() => {this.parentLinkNumber += 1;})

     Button(`Parent from LocalStorage ${this.parentLinkObject.code}`).onClick(() => {this.parentLinkObject.code += 1;})

     Child()

   }

 }

}

```


### (三)@LocalStorageProp和LocalStorage单向同步场景

在CompA组件和Child组件中创建与storage的'PropA'单向同步数据,如:

```typescript

let para: Record<string, number> = { 'PropA': 47 };

let storage: LocalStorage = new LocalStorage( para);

@Entry(storage)

@Component

struct CompA {

 @LocalStorageProp('PropA') storageProp1: number = 1;

 build() {

   Column({ space: 15 }) {

     Button(`Parent from LocalStorage ${this.storageProp1}`).onClick(() => {this.storageProp1 += 1})

     Child()

   }

 }

}

@Component

struct Child {

 @LocalStorageProp('PropA') storageProp2: number = 2;

 build() {

   Column({ space: 15 }) {

     Text(`Parent from LocalStorage ${this.storageProp2}`)

   }

 }

}

```


### (四)@LocalStorageLink和LocalStorage双向同步场景

如:

```typescript

let para: Record<string, number> = { 'PropA': 47 };

let storage: LocalStorage = new LocalStorage( para);

let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');

@Entry(storage)

@Component

struct CompA {

 @LocalStorageLink('PropA') storageLink: number = 1;

 build() {

   Column() {

     Text(`incr @LocalStorageLink variable`).onClick(() => {this.storageLink += 1})

     Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`)

   }

 }

}

```


### (五)兄弟组件之间同步状态变量

通过@LocalStorageLink实现兄弟组件状态同步,如:

```typescript

let ls: Record<string, number> = { 'countStorage': 1 }

let storage: LocalStorage = new LocalStorage(ls);

@Component

struct Child {

 label: string = 'no name';

 @LocalStorageLink('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(storage)

@Component

struct Parent {

 @LocalStorageLink('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('LocalStorage').width(50).height(60).fontSize(12)

       Text(`countStorage ${this.playCount} incr by 1`).onClick(() => {storage.set<number | undefined>('countStorage', Number(storage.get<number>('countStorage')) + 1);})

     }.width(300).height(60)

     Child({ label: 'ChildA' })

     Child({ label: 'ChildB' })

     Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`).width(300).height(60).fontSize(12)

   }

 }

}

```


### (六)将LocalStorage实例从UIAbility共享到多个视图

在UIAbility中创建LocalStorage实例并调用windowStage.loadContent共享,在页面中通过getShared获取,如:

1. **EntryAbility.ets中**:

```typescript

import { UIAbility } from '@kit.AbilityKit';

import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {

para:Record<string, number> = { 'PropA': 47 };

storage: LocalStorage = new LocalStorage(this.para);

onWindowStageCreate(windowStage: window.WindowStage) {

windowStage.loadContent('pages/Index', this.storage);

}

}

```

2. **index.ets中**:

```typescript

import { router } from '@kit.ArkUI';

let storage = LocalStorage.getShared()

@Entry(storage)

@Component

struct Index {

 @LocalStorageLink('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%')

 }

}

```

3. **Page.ets中**:

```typescript

import { router } from '@kit.ArkUI';

let storage = LocalStorage.getShared()

@Entry(storage)

@Component

struct Page {

 @LocalStorageLink('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%')

   }

 }

}

```


### (七)自定义组件接收LocalStorage实例

1. **定义属性时接收**:

   - 实例必须放在第二个参数位置传递,否则报错。

   - 如:

```typescript

let localStorage1: LocalStorage = new LocalStorage();

localStorage1.setOrCreate('PropA', 'PropA');

let localStorage2: LocalStorage = new LocalStorage();

localStorage2.setOrCreate('PropB', 'PropB');

@Entry(localStorage1)

@Component

struct Index {

 @LocalStorageLink('PropA') PropA: string = 'Hello World';

 @State count: number = 0;

 build() {

   Row() {

     Column() {

       Text(this.PropA).fontSize(50).fontWeight(FontWeight.Bold)

       Child({ count: this.count }, localStorage2)

     }.width('100%')

   }.height('100%')

 }

}

@Component

struct Child {

 @Link count: number;

 @LocalStorageLink('PropB') PropB: string = 'Hello World';

 build() {

   Text(this.PropB).fontSize(50).fontWeight(FontWeight.Bold)

 }

}

```

2. **未定义属性时接收**:可以只传入一个LocalStorage实例作为入参,如:

```typescript

let localStorage1: LocalStorage = new LocalStorage();

localStorage1.setOrCreate('PropA', 'PropA');

let localStorage2: LocalStorage = new LocalStorage();

localStorage2.setOrCreate('PropB', 'PropB');

@Entry(localStorage1)

@Component

struct Index {

 @LocalStorageLink('PropA') PropA: string = 'Hello World';

 @State count: number = 0;

 build() {

   Row() {

     Column() {

       Text(this.PropA).fontSize(50).fontWeight(FontWeight.Bold)

       Child(localStorage2)

     }.width('100%')

   }.height('100%')

 }

}

@Component

struct Child {

 build() {

   Text("hello").fontSize(50).fontWeight(FontWeight.Bold)

 }

}

```

3. **属性无需从父组件初始化时接收**:第一个参数传{},如:

```typescript

let localStorage1: LocalStorage = new LocalStorage();

localStorage1.setOrCreate('PropA', 'PropA');

let localStorage2: LocalStorage = new LocalStorage();

localStorage2.setOrCreate('PropB', 'PropB');

@Entry(localStorage1)

@Component

struct Index {

 @LocalStorageLink('PropA') PropA: string = 'Hello World';

 @State count: number = 0;

 build() {

   Row() {

     Column() {

       Text(this.PropA).fontSize(50).fontWeight(FontWeight.Bold)

       Child({}, localStorage2)

     }.width('100%')

   }.height('100%')

 }

}

@Component

struct Child {

 @State count: number = 5;

 @LocalStorageLink('PropB') PropB: string = 'Hello World';

 build() {

   Text(this.PropB).fontSize(50).fontWeight(FontWeight.Bold)

 }

}

```


### (八)Navigation组件和LocalStorage联合使用

通过传递不同LocalStorage实例给自定义组件,在navigation跳转时显示对应绑定值,如:

```typescript

let localStorageA: LocalStorage = new LocalStorage();

localStorageA.setOrCreate('PropA', 'PropA');

let localStorageB: LocalStorage = new LocalStorage();

localStorageB.setOrCreate('PropB', 'PropB');

let localStorageC: LocalStorage = new LocalStorage();

localStorageC.setOrCreate('PropC', 'PropC');

@Entry

@Component

struct MyNavigationTestStack {

 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();

 @Builder

 PageMap(name: string) {

   if (name === 'pageOne') {

     pageOneStack({}, localStorageA)

   } else if (name === 'pageTwo') {

     pageTwoStack({}, localStorageB)

   } else if (name === 'pageThree') {

     pageThreeStack({}, localStorageC)

   }

 }

 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;

 @LocalStorageLink('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;})

 }

}

```


## 六、总结

在鸿蒙 Next 中,页面级存储 LocalStorage 为开发者提供了一种方便的方式来管理页面级别的状态变量。通过使用 @LocalStorageProp 和 @LocalStorageLink 装饰器,开发者可以实现单向和双向的数据同步,满足不同的应用场景需求。无论是在单个页面内还是在多个页面之间,LocalStorage 都能够有效地共享状态,提高应用的开发效率和用户体验。同时,开发者需要注意 LocalStorage 的限制条件,以确保正确地使用和管理状态变量。


## 七、延伸内容

1. 在实际应用中,可以结合其他鸿蒙 Next 的特性和功能,如组件化开发、状态管理框架等,进一步优化应用的架构和性能。

2. 对于大型应用,可以考虑使用更复杂的状态管理方案,如 Redux 或 Vuex 的类似实现,以更好地管理全局状态。

3. 在使用 LocalStorage 时,要注意数据的安全性和隐私性,避免存储敏感信息。可以结合加密技术等手段来保护数据。

4. 不断探索和尝试新的用法和技巧,可以提高开发效率和代码质量。例如,可以使用自定义的 LocalStorage 实例来管理特定模块的状态,或者结合动画效果来增强用户体验。