一、概述
LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内的“数据库”。应用程序可以创建多个LocalStorage实例,其支持UIAbility实例内多个页面间状态共享,可在页面内共享,也能通过GetShared接口跨页面共享。组件树的根节点(被@Entry装饰的@Component)可被分配LocalStorage实例,其所有子组件实例将自动获得访问权限。LocalStorage中的属性都是可变的,其生命周期由应用程序决定。
二、限制条件
- 参数类型要求:@LocalStorageProp和@LocalStorageLink的参数必须为string类型,否则编译期会报错。例如:
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;
- 不支持Function类型变量:@StorageProp与@StorageLink不支持装饰Function类型的变量,框架会抛出运行时错误。
- 属性类型不可更改:LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。
- 页面级存储限制:getShared接口仅能获取当前Stage通过windowStage.loadContent传入的LocalStorage实例,否则返回undefined。
三、@LocalStorageProp装饰器
(一)装饰器使用规则
-
参数要求:
- key为常量字符串,必填且需带引号。
- 允许装饰的变量类型包括Object、class、string、number、boolean、enum类型及其数组,API12及以上支持Map、Set、Date类型等,不支持any,API12及以上支持undefined和null类型(建议显式指定类型)。例如:
@LocalStorageProp("AA") a: number | null = null; // 推荐
@LocalStorageProp("AA") a: number = null; // 不推荐
- 同步类型:与LocalStorage中key对应的属性建立单向数据同步,从LocalStorage到组件状态变量。即ArkUI框架支持修改@LocalStorageProp(key)在本地的值,但本地值的修改不会同步回LocalStorage中;而LocalStorage中key对应的属性值发生改变时,会同步给@LocalStorageProp(key)并覆盖本地值。
- 初始值要求:必须指定,若LocalStorage实例中不存在属性,则用该初始值初始化并存入LocalStorage。
(二)变量传递/访问规则
- 禁止从父节点初始化和更新:只能从LocalStorage中key对应的属性初始化,无对应key时用本地默认值。
- 支持初始化子节点:可用于初始化@State、@link、@prop、@Provide。
- 不支持组件外访问。
(三)观察变化和行为表现
-
观察变化类型:
- boolean、string、number类型可观察数值变化。
- class或Object类型可观察对象整体赋值和属性变化。
- array类型可观察数组添加、删除、更新单元变化。
- Date类型可观察整体赋值及通过相关接口更新属性。
- Map类型可观察整体赋值及通过接口更新值。
- Set类型可观察整体赋值及通过接口更新值。
-
框架行为:
- 组件内变量值变化不回写LocalStorage。
- 变量变化使关联组件刷新。
- LocalStorage中值变化会覆盖本地修改。
四、@LocalStorageLink装饰器
(一)装饰器使用规则
- 参数要求:同@LocalStorageProp,key为常量字符串,必填且带引号,变量类型要求相同。
- 同步类型:与LocalStorage中key对应的属性建立双向数据同步,即本地修改会写回LocalStorage,LocalStorage中的修改也会同步到绑定的属性上(包括单向和双向绑定变量)。
- 初始值要求:必须指定,若LocalStorage实例中不存在属性,则用该初始值初始化并存入LocalStorage。
(二)变量传递/访问规则
- 禁止从父节点初始化和更新:同@LocalStorageProp。
- 支持初始化子节点:同@LocalStorageProp。
- 不支持组件外访问。
(三)观察变化和行为表现
- 观察变化类型:与@LocalStorageProp相同。
-
框架行为:
- 组件内数值改变同步回LocalStorage。
- LocalStorage中值改变,绑定的数据(包括双向和单向)同步修改。
- 装饰的数据本身是状态变量时,改变会引起所属自定义组件重新渲染。
五、使用场景
(一)应用逻辑使用LocalStorage
通过创建LocalStorage实例,使用get、link、prop等接口操作属性,如:
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组件内部获取状态变量,如:
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'单向同步数据,如:
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双向同步场景
如:
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实现兄弟组件状态同步,如:
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获取,如:
- EntryAbility.ets中:
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);
}
}
- index.ets中:
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%')
}
}
- Page.ets中:
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实例
-
定义属性时接收:
- 实例必须放在第二个参数位置传递,否则报错。
- 如:
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)
}
}
- 未定义属性时接收:可以只传入一个LocalStorage实例作为入参,如:
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)
}
}
- 属性无需从父组件初始化时接收:第一个参数传{},如:
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跳转时显示对应绑定值,如:
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 的限制条件,以确保正确地使用和管理状态变量。
七、延伸内容
- 在实际应用中,可以结合其他鸿蒙 Next 的特性和功能,如组件化开发、状态管理框架等,进一步优化应用的架构和性能。
- 对于大型应用,可以考虑使用更复杂的状态管理方案,如 Redux 或 Vuex 的类似实现,以更好地管理全局状态。
- 在使用 LocalStorage 时,要注意数据的安全性和隐私性,避免存储敏感信息。可以结合加密技术等手段来保护数据。
- 不断探索和尝试新的用法和技巧,可以提高开发效率和代码质量。例如,可以使用自定义的 LocalStorage 实例来管理特定模块的状态,或者结合动画效果来增强用户体验。
Top comments (0)