1# LocalStorage:页面级UI状态存储 2 3 4LocalStorage是页面级的UI状态存储,通过\@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage支持UIAbility实例内多个页面间状态共享。 5 6 7本文仅介绍LocalStorage使用场景和相关的装饰器:\@LocalStorageProp和\@LocalStorageLink。 8 9 10> **说明:** 11> 12> LocalStorage从API version 9开始支持。 13 14 15## 概述 16 17LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内的“数据库”。 18 19- 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过GetShared接口,实现跨页面、UIAbility实例内共享。 20 21- 组件树的根节点,即被\@Entry装饰的\@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限。 22 23- 被\@Component装饰的组件最多可以访问一个LocalStorage实例和[AppStorage](arkts-appstorage.md),未被\@Entry装饰的组件不可被独立分配LocalStorage实例,只能接受父组件通过\@Entry传递来的LocalStorage实例。一个LocalStorage实例在组件树上可以被分配给多个组件。 24 25- LocalStorage中的所有属性都是可变的。 26 27应用程序决定LocalStorage对象的生命周期。当应用释放最后一个指向LocalStorage的引用时,比如销毁最后一个自定义组件,LocalStorage将被JS Engine垃圾回收。 28 29LocalStorage根据与\@Component装饰的组件的同步类型不同,提供了两个装饰器: 30 31- [@LocalStorageProp](#localstorageprop):\@LocalStorageProp装饰的变量与LocalStorage中给定属性建立单向同步关系。 32 33- [@LocalStorageLink](#localstoragelink):\@LocalStorageLink装饰的变量与LocalStorage中给定属性建立双向同步关系。 34 35 36## 限制条件 37 38- LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。 39- LocalStorage是页面级存储,[getShared](../reference/apis-arkui/arkui-ts/ts-state-management.md#getshared10)接口仅能获取当前Stage通过[windowStage.loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9)传入的LocalStorage实例,否则返回undefined。例子可见[将LocalStorage实例从UIAbility共享到一个或多个视图](#将localstorage实例从uiability共享到一个或多个视图)。 40 41 42## \@LocalStorageProp 43 44在上文中已经提到,如果要建立LocalStorage和自定义组件的联系,需要使用\@LocalStorageProp和\@LocalStorageLink装饰器。使用\@LocalStorageProp(key)/\@LocalStorageLink(key)装饰组件内的变量,key标识了LocalStorage的属性。 45 46 47当自定义组件初始化的时候,\@LocalStorageProp(key)/\@LocalStorageLink(key)装饰的变量会通过给定的key,绑定LocalStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证LocalStorage一定存在给定的key(这取决于应用逻辑是否在组件初始化之前在LocalStorage实例中存入对应的属性)。 48 49 50> **说明:** 51> 52> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 53> 54> 从API version 11开始,该装饰器支持在原子化服务中使用。 55 56\@LocalStorageProp(key)是和LocalStorage中key对应的属性建立单向数据同步,ArkUI框架支持修改@LocalStorageProp(key)在本地的值,但是对本地值的修改不会同步回LocalStorage中。相反,如果LocalStorage中key对应的属性值发生改变,例如通过set接口对LocalStorage中的值进行修改,改变会同步给\@LocalStorageProp(key),并覆盖掉本地的值。 57 58 59### 装饰器使用规则说明 60 61| \@LocalStorageProp变量装饰器 | 说明 | 62| ----------------------- | ---------------------------------------- | 63| 装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 | 64| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。<br/>类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。<br/>不支持any,API12及以上支持undefined和null类型。<br/>API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[LocalStorage支持联合类型](#localstorage支持联合类型)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@LocalStorageProp("AA") a: number \| null = null`是推荐的,不推荐`@LocalStorageProp("AA") a: number = null`。 | 65| 同步类型 | 单向同步:从LocalStorage的对应属性到组件的状态变量。组件本地的修改是允许的,但是LocalStorage中给定的属性一旦发生变化,将覆盖本地的修改。 | 66| 被装饰变量的初始值 | 必须指定,如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。 | 67 68 69### 变量的传递/访问规则说明 70 71| 传递/访问 | 说明 | 72| ---------- | ---------------------------------------- | 73| 从父节点初始化和更新 | 禁止,\@LocalStorageProp不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化。 | 74| 初始化子节点 | 支持,可用于初始化\@State、\@Link、\@Prop、\@Provide。 | 75| 是否支持组件外访问 | 否。 | 76 77 **图1** \@LocalStorageProp初始化规则图示 78 79 80 81 82### 观察变化和行为表现 83 84**观察变化** 85 86 87- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。 88 89- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用localstorage](#从ui内部使用localstorage))。 90 91- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。 92 93- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。 94 95- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 96 97- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 98 99 100**框架行为** 101 102 103- 被\@LocalStorageProp装饰的变量的值的变化不会同步回LocalStorage里。 104 105- \@LocalStorageProp装饰的变量变化会使当前自定义组件中关联的组件刷新。 106 107- LocalStorage(key)中值的变化会引发所有被\@LocalStorageProp对应key装饰的变量的变化,会覆盖\@LocalStorageProp本地的改变。 108 109 110 111 112## \@LocalStorageLink 113 114> **说明:** 115> 116> 从API version 11开始,该装饰器支持在原子化服务中使用。 117 118如果我们需要将自定义组件的状态变量的更新同步回LocalStorage,就需要用到\@LocalStorageLink。 119 120\@LocalStorageLink(key)是和LocalStorage中key对应的属性建立双向数据同步: 121 1221. 本地修改发生,该修改会被写回LocalStorage中; 123 1242. LocalStorage中的修改发生后,该修改会被同步到所有绑定LocalStorage对应key的属性上,包括单向(\@LocalStorageProp和通过prop创建的单向绑定变量)、双向(\@LocalStorageLink和通过link创建的双向绑定变量)变量。 125 126### 装饰器使用规则说明 127 128| \@LocalStorageLink变量装饰器 | 说明 | 129| ----------------------- | ---------------------------------------- | 130| 装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 | 131| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。<br/>类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。<br/>不支持any,API12及以上支持undefined和null类型。<br/>API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[LocalStorage支持联合类型](#localstorage支持联合类型)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@LocalStorageLink("AA") a: number \| null = null`是推荐的,不推荐`@LocalStorageLink("AA") a: number = null`。 | 132| 同步类型 | 双向同步:从LocalStorage的对应属性到自定义组件,从自定义组件到LocalStorage对应属性。 | 133| 被装饰变量的初始值 | 必须指定,如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。 | 134 135 136### 变量的传递/访问规则说明 137 138| 传递/访问 | 说明 | 139| ---------- | ---------------------------------------- | 140| 从父节点初始化和更新 | 禁止,\@LocalStorageLink不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化。 | 141| 初始化子节点 | 支持,可用于初始化\@State、\@Link、\@Prop、\@Provide。 | 142| 是否支持组件外访问 | 否。 | 143 144 145 **图2** \@LocalStorageLink初始化规则图示 146 147 148 149 150 151### 观察变化和行为表现 152 153**观察变化** 154 155 156- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。 157 158- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用localstorage](#从ui内部使用localstorage))。 159 160- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。 161 162- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。 163 164- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 165 166- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 167 168 169**框架行为** 170 171 1721. 当\@LocalStorageLink(key)装饰的数值改变被观察到时,修改将被同步回LocalStorage对应属性键值key的属性中。 173 1742. LocalStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向\@LocalStorageLink和单向\@LocalStorageProp)都将同步修改。 175 1763. 当\@LocalStorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回LocalStorage中,还会引起所属的自定义组件的重新渲染。 177 178 179 180 181## 使用场景 182 183 184### 应用逻辑使用LocalStorage 185 186 187```ts 188let para: Record<string,number> = { 'PropA': 47 }; 189let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化 190let propA: number | undefined = storage.get('PropA') // propA == 47 191let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47 192let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47 193let prop: SubscribedAbstractProperty<number> = storage.prop('PropA'); // prop.get() == 47 194link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48 195prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48 196link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49 197``` 198 199 200### 从UI内部使用LocalStorage 201 202除了应用程序逻辑使用LocalStorage,还可以借助LocalStorage相关的两个装饰器\@LocalStorageProp和\@LocalStorageLink,在UI组件内部获取到LocalStorage实例中存储的状态变量。 203 204本示例以\@LocalStorageLink为例,展示了: 205 206- 使用构造函数创建LocalStorage实例storage; 207 208- 使用\@Entry装饰器将storage添加到CompA顶层组件中; 209 210- \@LocalStorageLink绑定LocalStorage对给定的属性,建立双向数据同步。 211 212 ```ts 213class PropB { 214 code: number; 215 216 constructor(code: number) { 217 this.code = code; 218 } 219} 220// 创建新实例并使用给定对象初始化 221let para: Record<string, number> = { 'PropA': 47 }; 222let storage: LocalStorage = new LocalStorage(para); 223storage.setOrCreate('PropB', new PropB(50)); 224 225@Component 226struct Child { 227 // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定 228 @LocalStorageLink('PropA') childLinkNumber: number = 1; 229 // @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定 230 @LocalStorageLink('PropB') childLinkObject: PropB = new PropB(0); 231 232 build() { 233 Column() { 234 Button(`Child from LocalStorage ${this.childLinkNumber}`) // 更改将同步至LocalStorage中的'PropA'以及Parent.parentLinkNumber 235 .onClick(() => { 236 this.childLinkNumber += 1; 237 }) 238 Button(`Child from LocalStorage ${this.childLinkObject.code}`) // 更改将同步至LocalStorage中的'PropB'以及Parent.parentLinkObject.code 239 .onClick(() => { 240 this.childLinkObject.code += 1; 241 }) 242 } 243 } 244} 245// 使LocalStorage可从@Component组件访问 246@Entry(storage) 247@Component 248struct CompA { 249 // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定 250 @LocalStorageLink('PropA') parentLinkNumber: number = 1; 251 // @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定 252 @LocalStorageLink('PropB') parentLinkObject: PropB = new PropB(0); 253 254 build() { 255 Column({ space: 15 }) { 256 Button(`Parent from LocalStorage ${this.parentLinkNumber}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already 257 .onClick(() => { 258 this.parentLinkNumber += 1; 259 }) 260 261 Button(`Parent from LocalStorage ${this.parentLinkObject.code}`) // initial value from LocalStorage will be 50, because 'PropB' initialized already 262 .onClick(() => { 263 this.parentLinkObject.code += 1; 264 }) 265 // @Component子组件自动获得对CompA LocalStorage实例的访问权限。 266 Child() 267 } 268 } 269} 270``` 271 272 273### \@LocalStorageProp和LocalStorage单向同步的简单场景 274 275在下面的示例中,CompA 组件和Child组件分别在本地创建了与storage的'PropA'对应属性的单向同步的数据,我们可以看到: 276 277- CompA中对this.storageProp1的修改,只会在CompA中生效,并没有同步回storage; 278 279- Child组件中,Text绑定的storageProp2 依旧显示47。 280 281```ts 282// 创建新实例并使用给定对象初始化 283let para: Record<string, number> = { 'PropA': 47 }; 284let storage: LocalStorage = new LocalStorage(para); 285// 使LocalStorage可从@Component组件访问 286@Entry(storage) 287@Component 288struct CompA { 289 // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定 290 @LocalStorageProp('PropA') storageProp1: number = 1; 291 292 build() { 293 Column({ space: 15 }) { 294 // 点击后从47开始加1,只改变当前组件显示的storageProp1,不会同步到LocalStorage中 295 Button(`Parent from LocalStorage ${this.storageProp1}`) 296 .onClick(() => { 297 this.storageProp1 += 1 298 }) 299 Child() 300 } 301 } 302} 303 304@Component 305struct Child { 306 // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定 307 @LocalStorageProp('PropA') storageProp2: number = 2; 308 309 build() { 310 Column({ space: 15 }) { 311 // 当CompA改变时,当前storageProp2不会改变,显示47 312 Text(`Parent from LocalStorage ${this.storageProp2}`) 313 } 314 } 315} 316``` 317 318 319### \@LocalStorageLink和LocalStorage双向同步的简单场景 320 321下面的示例展示了\@LocalStorageLink装饰的数据和LocalStorage双向同步的场景: 322 323 324```ts 325// 构造LocalStorage实例 326let para: Record<string, number> = { 'PropA': 47 }; 327let storage: LocalStorage = new LocalStorage(para); 328// 调用link(api9以上)接口构造'PropA'的双向同步数据,linkToPropA 是全局变量 329let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA'); 330 331@Entry(storage) 332@Component 333struct CompA { 334 335 // @LocalStorageLink('PropA')在CompA自定义组件中创建'PropA'的双向同步数据,初始值为47,因为在构造LocalStorage已经给“PropA”设置47 336 @LocalStorageLink('PropA') storageLink: number = 1; 337 338 build() { 339 Column() { 340 Text(`incr @LocalStorageLink variable`) 341 // 点击“incr @LocalStorageLink variable”,this.storageLink加1,改变同步回storage,全局变量linkToPropA也会同步改变 342 343 .onClick(() => { 344 this.storageLink += 1 345 }) 346 347 // 并不建议在组件内使用全局变量linkToPropA.get(),因为可能会有生命周期不同引起的错误。 348 Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`) 349 } 350 } 351} 352``` 353 354 355### 兄弟组件之间同步状态变量 356 357下面的示例展示了通过\@LocalStorageLink双向同步兄弟组件之间的状态。 358 359先看Parent自定义组件中发生的变化: 360 3611. 点击“playCount ${this.playCount} dec by 1”,this.playCount减1,修改同步回LocalStorage中,Child组件中的playCountLink绑定的组件会同步刷新; 362 3632. 点击“countStorage ${this.playCount} incr by 1”,调用LocalStorage的set接口,更新LocalStorage中“countStorage”对应的属性,Child组件中的playCountLink绑定的组件会同步刷新; 364 3653. Text组件“playCount in LocalStorage for debug ${storage.get<number>('countStorage')}”没有同步刷新,因为storage.get<number>('countStorage')返回的是常规变量,常规变量的更新并不会引起Text组件的重新渲染。 366 367Child自定义组件中的变化: 368 3691. playCountLink的刷新会同步回LocalStorage,并且引起兄弟组件和父组件相应的刷新。 370 371```ts 372let ls: Record<string, number> = { 'countStorage': 1 } 373let storage: LocalStorage = new LocalStorage(ls); 374 375@Component 376struct Child { 377 // 子组件实例的名字 378 label: string = 'no name'; 379 // 和LocalStorage中“countStorage”的双向绑定数据 380 @LocalStorageLink('countStorage') playCountLink: number = 0; 381 382 build() { 383 Row() { 384 Text(this.label) 385 .width(50).height(60).fontSize(12) 386 Text(`playCountLink ${this.playCountLink}: inc by 1`) 387 .onClick(() => { 388 this.playCountLink += 1; 389 }) 390 .width(200).height(60).fontSize(12) 391 }.width(300).height(60) 392 } 393} 394 395@Entry(storage) 396@Component 397struct Parent { 398 @LocalStorageLink('countStorage') playCount: number = 0; 399 400 build() { 401 Column() { 402 Row() { 403 Text('Parent') 404 .width(50).height(60).fontSize(12) 405 Text(`playCount ${this.playCount} dec by 1`) 406 .onClick(() => { 407 this.playCount -= 1; 408 }) 409 .width(250).height(60).fontSize(12) 410 }.width(300).height(60) 411 412 Row() { 413 Text('LocalStorage') 414 .width(50).height(60).fontSize(12) 415 Text(`countStorage ${this.playCount} incr by 1`) 416 .onClick(() => { 417 storage.set<number | undefined>('countStorage', Number(storage.get<number>('countStorage')) + 1); 418 }) 419 .width(250).height(60).fontSize(12) 420 }.width(300).height(60) 421 422 Child({ label: 'ChildA' }) 423 Child({ label: 'ChildB' }) 424 425 Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`) 426 .width(300).height(60).fontSize(12) 427 } 428 } 429} 430``` 431 432 433### 将LocalStorage实例从UIAbility共享到一个或多个视图 434 435上面的实例中,LocalStorage的实例仅仅在一个\@Entry装饰的组件和其所属的子组件(一个页面)中共享,如果希望其在多个视图中共享,可以在所属UIAbility中创建LocalStorage实例,并调用windowStage.[loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9)。 436 437 438```ts 439// EntryAbility.ets 440import { UIAbility } from '@kit.AbilityKit'; 441import { window } from '@kit.ArkUI'; 442 443export default class EntryAbility extends UIAbility { 444para:Record<string, number> = { 'PropA': 47 }; 445storage: LocalStorage = new LocalStorage(this.para); 446 447onWindowStageCreate(windowStage: window.WindowStage) { 448windowStage.loadContent('pages/Index', this.storage); 449} 450} 451``` 452> **说明:** 453> 454> 在UI页面通过getShared接口获取通过loadContent共享的LocalStorage实例。 455> 456> LocalStorage.getShared()只在模拟器或者实机上才有效,在Previewer预览器中使用不生效。 457 458 459在下面的用例中,Index页面中的propA通过getShared()方法获取到共享的LocalStorage实例。点击Button跳转到Page页面,点击Change propA改变propA的值,back回Index页面后,页面中propA的值也同步修改。 460```ts 461// index.ets 462import { router } from '@kit.ArkUI'; 463 464// 通过getShared接口获取stage共享的LocalStorage实例 465let storage = LocalStorage.getShared() 466 467@Entry(storage) 468@Component 469struct Index { 470 // can access LocalStorage instance using 471 // @LocalStorageLink/Prop decorated variables 472 @LocalStorageLink('PropA') propA: number = 1; 473 474 build() { 475 Row() { 476 Column() { 477 Text(`${this.propA}`) 478 .fontSize(50) 479 .fontWeight(FontWeight.Bold) 480 Button("To Page") 481 .onClick(() => { 482 this.getUIContext().getRouter().pushUrl({ 483 url: 'pages/Page' 484 }) 485 }) 486 } 487 .width('100%') 488 } 489 .height('100%') 490 } 491} 492``` 493 494```ts 495// Page.ets 496import { router } from '@kit.ArkUI'; 497 498let storage = LocalStorage.getShared() 499 500@Entry(storage) 501@Component 502struct Page { 503 @LocalStorageLink('PropA') propA: number = 2; 504 505 build() { 506 Row() { 507 Column() { 508 Text(`${this.propA}`) 509 .fontSize(50) 510 .fontWeight(FontWeight.Bold) 511 512 Button("Change propA") 513 .onClick(() => { 514 this.propA = 100; 515 }) 516 517 Button("Back Index") 518 .onClick(() => { 519 this.getUIContext().getRouter().back() 520 }) 521 } 522 .width('100%') 523 } 524 } 525} 526``` 527 528> **说明:** 529> 530> 对于开发者更建议使用这个方式来构建LocalStorage的实例,并且在创建LocalStorage实例的时候就写入默认值,因为默认值可以作为运行异常的备份,也可以用作页面的单元测试。 531 532 533### 自定义组件接收LocalStorage实例 534 535除了根节点可通过@Entry来接收LocalStorage实例,自定义组件(子节点)也可以通过构造参数来传递LocalStorage实例。 536 537本示例以\@LocalStorageLink为例,展示了: 538 539- 父组件中的Text,显示LocalStorage实例localStorage1中PropA的值为“PropA”。 540 541- Child组件中,Text绑定的PropB,显示LocalStorage实例localStorage2中PropB的值为“PropB”。 542 543> **说明:** 544> 545> 从API version 12开始,自定义组件支持接收LocalStorage实例。 546> 当自定义组件作为子节点,定义了成员属性时,LocalStorage实例必须要放在第二个参数位置传递,否则会报类型不匹配的编译问题。 547> 当在自定义组件中定义了属性时,暂时不支持只有一个LocalStorage实例作为入参。如果没定义属性,可以只传入一个LocalStorage实例作为入参。 548> 如果定义的属性不需要从父组件初始化变量,则第一个参数需要传{}。 549> 作为构造参数传给子组件的LocalStorage实例在初始化时就会被决定,可以通过@LocalStorageLink或者LocalStorage的API修改LocalStorage实例中保存的属性值,但LocalStorage实例自身不能被动态修改。 550 551```ts 552let localStorage1: LocalStorage = new LocalStorage(); 553localStorage1.setOrCreate('PropA', 'PropA'); 554 555let localStorage2: LocalStorage = new LocalStorage(); 556localStorage2.setOrCreate('PropB', 'PropB'); 557 558@Entry(localStorage1) 559@Component 560struct Index { 561 // 'PropA',和localStorage1中'PropA'的双向同步 562 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 563 @State count: number = 0; 564 565 build() { 566 Row() { 567 Column() { 568 Text(this.PropA) 569 .fontSize(50) 570 .fontWeight(FontWeight.Bold) 571 // 使用LocalStorage 实例localStorage2 572 Child({ count: this.count }, localStorage2) 573 } 574 .width('100%') 575 } 576 .height('100%') 577 } 578} 579 580 581@Component 582struct Child { 583 @Link count: number; 584 // 'Hello World',和localStorage2中'PropB'的双向同步,localStorage2中没有'PropB',则使用默认值'Hello World' 585 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 586 587 build() { 588 Text(this.PropB) 589 .fontSize(50) 590 .fontWeight(FontWeight.Bold) 591 } 592} 593``` 594 5951. 当自定义组件没有定义属性时,可以只传入一个LocalStorage实例作为入参。 596 597```ts 598let localStorage1: LocalStorage = new LocalStorage(); 599localStorage1.setOrCreate('PropA', 'PropA'); 600 601let localStorage2: LocalStorage = new LocalStorage(); 602localStorage2.setOrCreate('PropB', 'PropB'); 603 604@Entry(localStorage1) 605@Component 606struct Index { 607 // 'PropA',和localStorage1中'PropA'的双向同步 608 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 609 @State count: number = 0; 610 611 build() { 612 Row() { 613 Column() { 614 Text(this.PropA) 615 .fontSize(50) 616 .fontWeight(FontWeight.Bold) 617 // 使用LocalStorage 实例localStorage2 618 Child(localStorage2) 619 } 620 .width('100%') 621 } 622 .height('100%') 623 } 624} 625 626 627@Component 628struct Child { 629 build() { 630 Text("hello") 631 .fontSize(50) 632 .fontWeight(FontWeight.Bold) 633 } 634} 635``` 636 6372. 当定义的属性不需要从父组件初始化变量时,第一个参数需要传{}。 638 639```ts 640let localStorage1: LocalStorage = new LocalStorage(); 641localStorage1.setOrCreate('PropA', 'PropA'); 642 643let localStorage2: LocalStorage = new LocalStorage(); 644localStorage2.setOrCreate('PropB', 'PropB'); 645 646@Entry(localStorage1) 647@Component 648struct Index { 649 // 'PropA',和localStorage1中'PropA'的双向同步 650 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 651 @State count: number = 0; 652 653 build() { 654 Row() { 655 Column() { 656 Text(this.PropA) 657 .fontSize(50) 658 .fontWeight(FontWeight.Bold) 659 // 使用LocalStorage 实例localStorage2 660 Child({}, localStorage2) 661 } 662 .width('100%') 663 } 664 .height('100%') 665 } 666} 667 668 669@Component 670struct Child { 671 @State count: number = 5; 672 // 'Hello World',和localStorage2中'PropB'的双向同步,localStorage2中没有'PropB',则使用默认值'Hello World' 673 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 674 675 build() { 676 Text(this.PropB) 677 .fontSize(50) 678 .fontWeight(FontWeight.Bold) 679 } 680} 681``` 682 683 684### Navigation组件和LocalStorage联合使用 685 686可以通过传递不同的LocalStorage实例给自定义组件,从而实现在navigation跳转到不同的页面时,绑定不同的LocalStorage实例,显示对应绑定的值。 687 688本示例以\@LocalStorageLink为例,展示了: 689 690- 点击父组件中的Button "Next Page",创建并跳转到name为"pageOne"的子页面,Text显示信息为LocalStorage实例localStorageA中绑定的PropA的值,为"PropA"。 691 692- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageTwo"的子页面,Text显示信息为LocalStorage实例localStorageB中绑定的PropB的值,为"PropB"。 693 694- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageTree"的子页面,Text显示信息为LocalStorage实例localStorageC中绑定的PropC的值,为"PropC"。 695 696- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageOne"的子页面,Text显示信息为LocalStorage实例localStorageA中绑定的PropA的值,为"PropA"。 697 698- NavigationContentMsgStack自定义组件中的Text组件,共享对应自定义组件树上LocalStorage实例绑定的PropA的值。 699 700 701```ts 702let localStorageA: LocalStorage = new LocalStorage(); 703localStorageA.setOrCreate('PropA', 'PropA'); 704 705let localStorageB: LocalStorage = new LocalStorage(); 706localStorageB.setOrCreate('PropB', 'PropB'); 707 708let localStorageC: LocalStorage = new LocalStorage(); 709localStorageC.setOrCreate('PropC', 'PropC'); 710 711@Entry 712@Component 713struct MyNavigationTestStack { 714 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 715 716 @Builder 717 PageMap(name: string) { 718 if (name === 'pageOne') { 719 // 传递不同的LocalStorage实例 720 pageOneStack({}, localStorageA) 721 } else if (name === 'pageTwo') { 722 pageTwoStack({}, localStorageB) 723 } else if (name === 'pageThree') { 724 pageThreeStack({}, localStorageC) 725 } 726 } 727 728 build() { 729 Column({ space: 5 }) { 730 Navigation(this.pageInfo) { 731 Column() { 732 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 733 .width('80%') 734 .height(40) 735 .margin(20) 736 .onClick(() => { 737 this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈 738 }) 739 } 740 }.title('NavIndex') 741 .navDestination(this.PageMap) 742 .mode(NavigationMode.Stack) 743 .borderWidth(1) 744 } 745 } 746} 747 748@Component 749struct pageOneStack { 750 @Consume('pageInfo') pageInfo: NavPathStack; 751 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 752 753 build() { 754 NavDestination() { 755 Column() { 756 NavigationContentMsgStack() 757 // 显示绑定的LocalStorage中PropA的值'PropA' 758 Text(`${this.PropA}`) 759 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 760 .width('80%') 761 .height(40) 762 .margin(20) 763 .onClick(() => { 764 this.pageInfo.pushPathByName('pageTwo', null); 765 }) 766 }.width('100%').height('100%') 767 }.title('pageOne') 768 .onBackPressed(() => { 769 this.pageInfo.pop(); 770 return true; 771 }) 772 } 773} 774 775@Component 776struct pageTwoStack { 777 @Consume('pageInfo') pageInfo: NavPathStack; 778 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 779 780 build() { 781 NavDestination() { 782 Column() { 783 NavigationContentMsgStack() 784 // 绑定的LocalStorage中没有PropA,显示本地初始化的值 'Hello World' 785 Text(`${this.PropB}`) 786 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 787 .width('80%') 788 .height(40) 789 .margin(20) 790 .onClick(() => { 791 this.pageInfo.pushPathByName('pageThree', null); 792 }) 793 794 }.width('100%').height('100%') 795 }.title('pageTwo') 796 .onBackPressed(() => { 797 this.pageInfo.pop(); 798 return true; 799 }) 800 } 801} 802 803@Component 804struct pageThreeStack { 805 @Consume('pageInfo') pageInfo: NavPathStack; 806 @LocalStorageLink('PropC') PropC: string = 'pageThreeStack'; 807 808 build() { 809 NavDestination() { 810 Column() { 811 NavigationContentMsgStack() 812 813 // 绑定的LocalStorage中没有PropA,显示本地初始化的值 'pageThreeStack' 814 Text(`${this.PropC}`) 815 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 816 .width('80%') 817 .height(40) 818 .margin(20) 819 .onClick(() => { 820 this.pageInfo.pushPathByName('pageOne', null); 821 }) 822 823 }.width('100%').height('100%') 824 }.title('pageThree') 825 .onBackPressed(() => { 826 this.pageInfo.pop(); 827 return true; 828 }) 829 } 830} 831 832@Component 833struct NavigationContentMsgStack { 834 @LocalStorageLink('PropA') PropA: string = 'Hello'; 835 836 build() { 837 Column() { 838 Text(`${this.PropA}`) 839 .fontSize(30) 840 .fontWeight(FontWeight.Bold) 841 } 842 } 843} 844``` 845 846 847### LocalStorage支持联合类型 848 849在下面的示例中,变量A的类型为number | null,变量B的类型为number | undefined。Text组件初始化分别显示为null和undefined,点击切换为数字,再次点击切换回null和undefined。 850 851```ts 852@Component 853struct LocalStorLink { 854 @LocalStorageLink("AA") A: number | null = null; 855 @LocalStorageLink("BB") B: number | undefined = undefined; 856 857 build() { 858 Column() { 859 Text("@LocalStorageLink接口初始化,@LocalStorageLink取值") 860 Text(this.A + "").fontSize(20).onClick(() => { 861 this.A ? this.A = null : this.A = 1; 862 }) 863 Text(this.B + "").fontSize(20).onClick(() => { 864 this.B ? this.B = undefined : this.B = 1; 865 }) 866 } 867 .borderWidth(3).borderColor(Color.Green) 868 869 } 870} 871 872@Component 873struct LocalStorProp { 874 @LocalStorageProp("AAA") A: number | null = null; 875 @LocalStorageProp("BBB") B: number | undefined = undefined; 876 877 build() { 878 Column() { 879 Text("@LocalStorageProp接口初始化,@LocalStorageProp取值") 880 Text(this.A + "").fontSize(20).onClick(() => { 881 this.A ? this.A = null : this.A = 1; 882 }) 883 Text(this.B + "").fontSize(20).onClick(() => { 884 this.B ? this.B = undefined : this.B = 1; 885 }) 886 } 887 .borderWidth(3).borderColor(Color.Yellow) 888 889 } 890} 891 892let storage1: LocalStorage = new LocalStorage(); 893 894@Entry(storage1) 895@Component 896struct TestCase3 { 897 build() { 898 Row() { 899 Column() { 900 LocalStorLink() 901 LocalStorProp() 902 } 903 .width('100%') 904 } 905 .height('100%') 906 } 907} 908``` 909 910 911### 装饰Date类型变量 912 913> **说明:** 914> 915> 从API version 12开始,LocalStorage支持Date类型。 916 917在下面的示例中,@LocalStorageLink装饰的selectedDate类型为Date,点击Button改变selectedDate的值,视图会随之刷新。 918 919```ts 920@Entry 921@Component 922struct LocalDateSample { 923 @LocalStorageLink("date") selectedDate: Date = new Date('2021-08-08'); 924 925 build() { 926 Column() { 927 Button('set selectedDate to 2023-07-08') 928 .margin(10) 929 .onClick(() => { 930 this.selectedDate = new Date('2023-07-08'); 931 }) 932 Button('increase the year by 1') 933 .margin(10) 934 .onClick(() => { 935 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 936 }) 937 Button('increase the month by 1') 938 .margin(10) 939 .onClick(() => { 940 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1); 941 }) 942 Button('increase the day by 1') 943 .margin(10) 944 .onClick(() => { 945 this.selectedDate.setDate(this.selectedDate.getDate() + 1); 946 }) 947 DatePicker({ 948 start: new Date('1970-1-1'), 949 end: new Date('2100-1-1'), 950 selected: $$this.selectedDate 951 }) 952 }.width('100%') 953 } 954} 955``` 956 957 958### 装饰Map类型变量 959 960> **说明:** 961> 962> 从API version 12开始,LocalStorage支持Map类型。 963 964在下面的示例中,@LocalStorageLink装饰的message类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。 965 966```ts 967@Entry 968@Component 969struct LocalMapSample { 970 @LocalStorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 971 972 build() { 973 Row() { 974 Column() { 975 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 976 Text(`${item[0]}`).fontSize(30) 977 Text(`${item[1]}`).fontSize(30) 978 Divider() 979 }) 980 Button('init map').onClick(() => { 981 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]); 982 }) 983 Button('set new one').onClick(() => { 984 this.message.set(4, "d"); 985 }) 986 Button('clear').onClick(() => { 987 this.message.clear(); 988 }) 989 Button('replace the existing one').onClick(() => { 990 this.message.set(0, "aa"); 991 }) 992 Button('delete the existing one').onClick(() => { 993 this.message.delete(0); 994 }) 995 } 996 .width('100%') 997 } 998 .height('100%') 999 } 1000} 1001``` 1002 1003 1004### 装饰Set类型变量 1005 1006> **说明:** 1007> 1008> 从API version 12开始,LocalStorage支持Set类型。 1009 1010在下面的示例中,@LocalStorageLink装饰的memberSet类型为Set\<number\>,点击Button改变memberSet的值,视图会随之刷新。 1011 1012```ts 1013@Entry 1014@Component 1015struct LocalSetSample { 1016 @LocalStorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]); 1017 1018 build() { 1019 Row() { 1020 Column() { 1021 ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => { 1022 Text(`${item[0]}`) 1023 .fontSize(30) 1024 Divider() 1025 }) 1026 Button('init set') 1027 .onClick(() => { 1028 this.memberSet = new Set([0, 1, 2, 3, 4]); 1029 }) 1030 Button('set new one') 1031 .onClick(() => { 1032 this.memberSet.add(5); 1033 }) 1034 Button('clear') 1035 .onClick(() => { 1036 this.memberSet.clear(); 1037 }) 1038 Button('delete the first one') 1039 .onClick(() => { 1040 this.memberSet.delete(0); 1041 }) 1042 } 1043 .width('100%') 1044 } 1045 .height('100%') 1046 } 1047} 1048``` 1049 1050### 自定义组件外改变状态变量 1051 1052```ts 1053let storage = new LocalStorage(); 1054storage.setOrCreate('count', 47); 1055 1056class Model { 1057 storage: LocalStorage = storage; 1058 1059 call(propName: string, value: number) { 1060 this.storage.setOrCreate<number>(propName, value); 1061 } 1062} 1063 1064let model: Model = new Model(); 1065 1066@Entry({ storage: storage }) 1067@Component 1068struct Test { 1069 @LocalStorageLink('count') count: number = 0; 1070 1071 build() { 1072 Column() { 1073 Text(`count值: ${this.count}`) 1074 Button('change') 1075 .onClick(() => { 1076 model.call('count', this.count + 1); 1077 }) 1078 } 1079 } 1080} 1081``` 1082 1083<!--no_check--> 1084