1# AppStorage:应用全局的UI状态存储 2 3 4AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。 5 6 7和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的“中枢”,[持久化数据PersistentStorage](arkts-persiststorage.md)和[环境变量Environment](arkts-environment.md)都是通过AppStorage中转,才可以和UI交互。 8 9 10本文仅介绍AppStorage使用场景和相关的装饰器:\@StorageProp和\@StorageLink。 11 12 13## 概述 14 15AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。 16 17AppStorage可以和UI组件同步,且可以在应用业务逻辑中被访问。 18 19AppStorage支持应用的[主线程](../application-models/thread-model-stage.md)内多个UIAbility实例间的状态共享。 20 21AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化(详见[PersistentStorage](arkts-persiststorage.md))。这些数据是通过业务逻辑中实现,与UI解耦,如果希望这些数据在UI中使用,需要用到[@StorageProp](#storageprop)和[@StorageLink](#storagelink)。 22 23 24## \@StorageProp 25 26在上文中已经提到,如果要建立AppStorage和自定义组件的联系,需要使用\@StorageProp和\@StorageLink装饰器。使用\@StorageProp(key)/\@StorageLink(key)装饰组件内的变量,key标识了AppStorage的属性。 27 28当自定义组件初始化的时候,会使用AppStorage中对应key的属性值将\@StorageProp(key)/\@StorageLink(key)装饰的变量初始化。由于应用逻辑的差异,无法确认是否在组件初始化之前向AppStorage实例中存入了对应的属性,所以AppStorage不一定存在key对应的属性,因此\@StorageProp(key)/\@StorageLink(key)装饰的变量进行本地初始化是必要的。 29 30\@StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步,允许本地改变,但是对于\@StorageProp,本地的修改永远不会同步回AppStorage中,相反,如果AppStorage给定key的属性发生改变,改变会被同步给\@StorageProp,并覆盖掉本地的修改。 31> **说明:** 32> 33> 从API version 11开始,该装饰器支持在原子化服务中使用。 34 35### 装饰器使用规则说明 36 37| \@StorageProp变量装饰器 | 说明 | 38| ----------------------- | ------------------------------------------------------------ | 39| 装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 | 40| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。<br/>类型必须被指定,建议和AppStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。<br/>不支持any,API12及以上支持undefined和null类型。<br/>API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[AppStorage支持联合类型](#appstorage支持联合类型)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@StorageProp("AA") a: number \| null = null`是推荐的,不推荐`@StorageProp("AA") a: number = null`。 | 41| 同步类型 | 单向同步:从AppStorage的对应属性到组件的状态变量。<br/>组件本地的修改是允许的,但是AppStorage中给定的属性一旦发生变化,将覆盖本地的修改。 | 42| 被装饰变量的初始值 | 必须指定,如果AppStorage实例中不存在属性,则用该初始值初始化该属性,并存入AppStorage中。 | 43 44 45### 变量的传递/访问规则说明 46 47| 传递/访问 | 说明 | 48| ---------- | ---------------------------------------- | 49| 从父节点初始化和更新 | 禁止,\@StorageProp不支持从父节点初始化,只能AppStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化 | 50| 初始化子节点 | 支持,可用于初始化\@State、\@Link、\@Prop、\@Provide。 | 51| 是否支持组件外访问 | 否。 | 52 53 54 **图1** \@StorageProp初始化规则图示 55 56 57 58 59 60### 观察变化和行为表现 61 62**观察变化** 63 64 65- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。 66 67- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用appstorage和localstorage](#从ui内部使用appstorage和localstorage))。 68 69- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。 70 71- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。 72 73- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 74 75- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 76 77 78**框架行为** 79 80 81- 当\@StorageProp(key)装饰的数值改变被观察到时,修改不会被同步回AppStorage对应key的属性中。 82 83- 当前\@StorageProp(key)单向绑定的数据会被修改,即仅限于当前组件的私有成员变量改变,其他绑定该key的数据不会同步改变。 84 85- 当\@StorageProp(key)装饰的数据本身是状态变量,它的改变虽然不会同步回AppStorage中,但是会引起所属的自定义组件重新渲染。 86 87- 当AppStorage中key对应的属性发生改变时,会同步给所有\@StorageProp(key)装饰的数据,\@StorageProp(key)本地的修改将被覆盖。 88 89 90## \@StorageLink 91 92> **说明:** 93> 94> 从API version 11开始,该装饰器支持在原子化服务中使用。 95 96\@StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步: 97 981. 本地修改发生,该修改会被写回AppStorage中; 99 1002. AppStorage中的修改发生后,该修改会被同步到所有绑定AppStorage对应key的属性上,包括单向(\@StorageProp和通过Prop创建的单向绑定变量)、双向(\@StorageLink和通过Link创建的双向绑定变量)变量和其他实例(比如PersistentStorage)。 101 102### 装饰器使用规则说明 103 104| \@StorageLink变量装饰器 | 说明 | 105| ------------------ | ---------------------------------------- | 106| 装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 | 107| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。<br/>类型必须被指定,建议和AppStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。<br/>不支持any,API12及以上支持undefined和null类型。<br/>API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[AppStorage支持联合类型](#appstorage支持联合类型)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@StorageLink("AA") a: number \| null = null`是推荐的,不推荐`@StorageLink("AA") a: number = null`。 | 108| 同步类型 | 双向同步:从AppStorage的对应属性到自定义组件,从自定义组件到AppStorage对应属性。 | 109| 被装饰变量的初始值 | 必须指定,如果AppStorage实例中不存在属性,则用该初始值初始化该属性,并存入AppStorage中。 | 110 111 112### 变量的传递/访问规则说明 113 114| 传递/访问 | 说明 | 115| ---------- | ---------------------------------------- | 116| 从父节点初始化和更新 | 禁止。 | 117| 初始化子节点 | 支持,可用于初始化常规变量、\@State、\@Link、\@Prop、\@Provide。 | 118| 是否支持组件外访问 | 否。 | 119 120 121 **图2** \@StorageLink初始化规则图示 122 123 124 125 126 127### 观察变化和行为表现 128 129**观察变化** 130 131 132- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。 133 134- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用appstorage和localstorage](#从ui内部使用appstorage和localstorage))。 135 136- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。 137 138- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。 139 140- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。 141 142- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。 143 144 145**框架行为** 146 147 1481. 当\@StorageLink(key)装饰的数值改变被观察到时,修改将被同步回AppStorage对应属性键值key的属性中。 149 1502. AppStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向\@StorageLink和单向\@StorageProp)都将同步修改。 151 1523. 当\@StorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回AppStorage中,还会引起所属的自定义组件的重新渲染。 153 154 155## 使用场景 156 157 158### 从应用逻辑使用AppStorage和LocalStorage 159 160AppStorage是单例,它的所有API都是静态的,使用方法类似于LocalStorage中对应的非静态方法。 161 162 163```ts 164AppStorage.setOrCreate('PropA', 47); 165 166let storage: LocalStorage = new LocalStorage(); 167storage.setOrCreate('PropA',17); 168let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17 169let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47 170let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47 171let prop: SubscribedAbstractProperty<number> = AppStorage.prop('PropA'); // prop.get() == 47 172 173link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48 174prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48 175link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49 176 177storage.get<number>('PropA') // == 17 178storage.set('PropA', 101); 179storage.get<number>('PropA') // == 101 180 181AppStorage.get<number>('PropA') // == 49 182link1.get() // == 49 183link2.get() // == 49 184prop.get() // == 49 185``` 186 187 188### 从UI内部使用AppStorage和LocalStorage 189 190\@StorageLink变量装饰器与AppStorage配合使用,正如\@LocalStorageLink与LocalStorage配合使用一样。此装饰器使用AppStorage中的属性创建双向数据同步。 191 192 193```ts 194class PropB { 195 code: number; 196 197 constructor(code: number) { 198 this.code = code; 199 } 200} 201 202AppStorage.setOrCreate('PropA', 47); 203AppStorage.setOrCreate('PropB', new PropB(50)); 204let storage = new LocalStorage(); 205storage.setOrCreate('PropA', 48); 206storage.setOrCreate('PropB', new PropB(100)); 207 208@Entry(storage) 209@Component 210struct CompA { 211 @StorageLink('PropA') storageLink: number = 1; 212 @LocalStorageLink('PropA') localStorageLink: number = 1; 213 @StorageLink('PropB') storageLinkObject: PropB = new PropB(1); 214 @LocalStorageLink('PropB') localStorageLinkObject: PropB = new PropB(1); 215 216 build() { 217 Column({ space: 20 }) { 218 Text(`From AppStorage ${this.storageLink}`) 219 .onClick(() => { 220 this.storageLink += 1; 221 }) 222 223 Text(`From LocalStorage ${this.localStorageLink}`) 224 .onClick(() => { 225 this.localStorageLink += 1; 226 }) 227 228 Text(`From AppStorage ${this.storageLinkObject.code}`) 229 .onClick(() => { 230 this.storageLinkObject.code += 1; 231 }) 232 233 Text(`From LocalStorage ${this.localStorageLinkObject.code}`) 234 .onClick(() => { 235 this.localStorageLinkObject.code += 1; 236 }) 237 } 238 } 239} 240``` 241 242### 不建议借助@StorageLink的双向同步机制实现事件通知 243 244不建议开发者使用@StorageLink和AppStorage的双向同步的机制来实现事件通知,因为AppStorage中的变量可能绑定在多个不同页面的组件中,但事件通知则不一定需要通知到所有的这些组件。并且,当这些@StorageLink装饰的变量在UI中使用时,会触发UI刷新,带来不必要的性能影响。 245 246示例代码中,TapImage中的点击事件,会触发AppStorage中tapIndex对应属性的改变。因为@StorageLink是双向同步,修改会同步回AppStorage中,所以,所有绑定AppStorage的tapIndex自定义组件里都能感知到tapIndex的变化。使用@Watch监听到tapIndex的变化后,修改状态变量tapColor从而触发UI刷新(此处tapIndex并未直接绑定在UI上,因此tapIndex的变化不会直接触发UI刷新)。 247 248使用该机制来实现事件通知需要确保AppStorage中的变量尽量不要直接绑定在UI上,且需要控制@Watch函数的复杂度(如果@Watch函数执行时间长,会影响UI刷新效率)。 249 250 251```ts 252// xxx.ets 253class ViewData { 254 title: string; 255 uri: Resource; 256 color: Color = Color.Black; 257 258 constructor(title: string, uri: Resource) { 259 this.title = title; 260 this.uri = uri 261 } 262} 263 264@Entry 265@Component 266struct Gallery2 { 267 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 268 scroller: Scroller = new Scroller() 269 270 build() { 271 Column() { 272 Grid(this.scroller) { 273 ForEach(this.dataList, (item: ViewData, index?: number) => { 274 GridItem() { 275 TapImage({ 276 uri: item.uri, 277 index: index 278 }) 279 }.aspectRatio(1) 280 281 }, (item: ViewData, index?: number) => { 282 return JSON.stringify(item) + index; 283 }) 284 }.columnsTemplate('1fr 1fr') 285 } 286 287 } 288} 289 290@Component 291export struct TapImage { 292 @StorageLink('tapIndex') @Watch('onTapIndexChange') tapIndex: number = -1; 293 @State tapColor: Color = Color.Black; 294 private index: number = 0; 295 private uri: Resource = { 296 id: 0, 297 type: 0, 298 moduleName: "", 299 bundleName: "" 300 }; 301 302 // 判断是否被选中 303 onTapIndexChange() { 304 if (this.tapIndex >= 0 && this.index === this.tapIndex) { 305 console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, red`) 306 this.tapColor = Color.Red; 307 } else { 308 console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, black`) 309 this.tapColor = Color.Black; 310 } 311 } 312 313 build() { 314 Column() { 315 Image(this.uri) 316 .objectFit(ImageFit.Cover) 317 .onClick(() => { 318 this.tapIndex = this.index; 319 }) 320 .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor }) 321 } 322 323 } 324} 325``` 326 327相比借助@StorageLink的双向同步机制实现事件通知,开发者可以使用emit订阅某个事件并接收事件回调的方式来减少开销,增强代码的可读性。 328 329> **说明:** 330> 331> emit接口不支持在Previewer预览器中使用。 332 333 334```ts 335// xxx.ets 336import { emitter } from '@kit.BasicServicesKit'; 337 338let NextID: number = 0; 339 340class ViewData { 341 title: string; 342 uri: Resource; 343 color: Color = Color.Black; 344 id: number; 345 346 constructor(title: string, uri: Resource) { 347 this.title = title; 348 this.uri = uri 349 this.id = NextID++; 350 } 351} 352 353@Entry 354@Component 355struct Gallery2 { 356 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 357 scroller: Scroller = new Scroller() 358 private preIndex: number = -1 359 360 build() { 361 Column() { 362 Grid(this.scroller) { 363 ForEach(this.dataList, (item: ViewData) => { 364 GridItem() { 365 TapImage({ 366 uri: item.uri, 367 index: item.id 368 }) 369 }.aspectRatio(1) 370 .onClick(() => { 371 if (this.preIndex === item.id) { 372 return 373 } 374 let innerEvent: emitter.InnerEvent = { eventId: item.id } 375 // 选中态:黑变红 376 let eventData: emitter.EventData = { 377 data: { 378 "colorTag": 1 379 } 380 } 381 emitter.emit(innerEvent, eventData) 382 383 if (this.preIndex != -1) { 384 console.info(`preIndex: ${this.preIndex}, index: ${item.id}, black`) 385 let innerEvent: emitter.InnerEvent = { eventId: this.preIndex } 386 // 取消选中态:红变黑 387 let eventData: emitter.EventData = { 388 data: { 389 "colorTag": 0 390 } 391 } 392 emitter.emit(innerEvent, eventData) 393 } 394 this.preIndex = item.id 395 }) 396 }, (item: ViewData) => JSON.stringify(item)) 397 }.columnsTemplate('1fr 1fr') 398 } 399 400 } 401} 402 403@Component 404export struct TapImage { 405 @State tapColor: Color = Color.Black; 406 private index: number = 0; 407 private uri: Resource = { 408 id: 0, 409 type: 0, 410 moduleName: "", 411 bundleName: "" 412 }; 413 414 onTapIndexChange(colorTag: emitter.EventData) { 415 if (colorTag.data != null) { 416 this.tapColor = colorTag.data.colorTag ? Color.Red : Color.Black 417 } 418 } 419 420 aboutToAppear() { 421 //定义事件ID 422 let innerEvent: emitter.InnerEvent = { eventId: this.index } 423 emitter.on(innerEvent, data => { 424 this.onTapIndexChange(data) 425 }) 426 } 427 428 build() { 429 Column() { 430 Image(this.uri) 431 .objectFit(ImageFit.Cover) 432 .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor }) 433 } 434 } 435} 436``` 437 438以上通知事件逻辑简单,也可以简化成三元表达式。 439 440```ts 441// xxx.ets 442class ViewData { 443 title: string; 444 uri: Resource; 445 color: Color = Color.Black; 446 447 constructor(title: string, uri: Resource) { 448 this.title = title; 449 this.uri = uri 450 } 451} 452 453@Entry 454@Component 455struct Gallery2 { 456 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 457 scroller: Scroller = new Scroller() 458 459 build() { 460 Column() { 461 Grid(this.scroller) { 462 ForEach(this.dataList, (item: ViewData, index?: number) => { 463 GridItem() { 464 TapImage({ 465 uri: item.uri, 466 index: index 467 }) 468 }.aspectRatio(1) 469 470 }, (item: ViewData, index?: number) => { 471 return JSON.stringify(item) + index; 472 }) 473 }.columnsTemplate('1fr 1fr') 474 } 475 476 } 477} 478 479@Component 480export struct TapImage { 481 @StorageLink('tapIndex') tapIndex: number = -1; 482 private index: number = 0; 483 private uri: Resource = { 484 id: 0, 485 type: 0, 486 moduleName: "", 487 bundleName: "" 488 }; 489 490 build() { 491 Column() { 492 Image(this.uri) 493 .objectFit(ImageFit.Cover) 494 .onClick(() => { 495 this.tapIndex = this.index; 496 }) 497 .border({ 498 width: 5, 499 style: BorderStyle.Dotted, 500 color: (this.tapIndex >= 0 && this.index === this.tapIndex) ? Color.Red : Color.Black 501 }) 502 } 503 } 504} 505``` 506 507 508### AppStorage支持联合类型 509 510在下面的示例中,变量A的类型为number | null,变量B的类型为number | undefined。Text组件初始化分别显示为null和undefined,点击切换为数字,再次点击切换回null和undefined。 511 512```ts 513@Component 514struct StorLink { 515 @StorageLink("AA") A: number | null = null; 516 @StorageLink("BB") B: number | undefined = undefined; 517 518 build() { 519 Column() { 520 Text("@StorageLink接口初始化,@StorageLink取值") 521 Text(this.A + "").fontSize(20).onClick(() => { 522 this.A ? this.A = null : this.A = 1; 523 }) 524 Text(this.B + "").fontSize(20).onClick(() => { 525 this.B ? this.B = undefined : this.B = 1; 526 }) 527 } 528 .borderWidth(3).borderColor(Color.Red) 529 530 } 531} 532 533@Component 534struct StorProp { 535 @StorageProp("AAA") A: number | null = null; 536 @StorageProp("BBB") B: number | undefined = undefined; 537 538 build() { 539 Column() { 540 Text("@StorageProp接口初始化,@StorageProp取值") 541 Text(this.A + "").fontSize(20).onClick(() => { 542 this.A ? this.A = null : this.A = 1; 543 }) 544 Text(this.B + "").fontSize(20).onClick(() => { 545 this.B ? this.B = undefined : this.B = 1; 546 }) 547 } 548 .borderWidth(3).borderColor(Color.Blue) 549 } 550} 551 552@Entry 553@Component 554struct TestCase3 { 555 build() { 556 Row() { 557 Column() { 558 StorLink() 559 StorProp() 560 } 561 .width('100%') 562 } 563 .height('100%') 564 } 565} 566``` 567 568 569### 装饰Date类型变量 570 571> **说明:** 572> 573> 从API version 12开始,AppStorage支持Date类型。 574 575在下面的示例中,@StorageLink装饰的selectedDate类型为Date,点击Button改变selectedDate的值,视图会随之刷新。 576 577```ts 578@Entry 579@Component 580struct DateSample { 581 @StorageLink("date") selectedDate: Date = new Date('2021-08-08'); 582 583 build() { 584 Column() { 585 Button('set selectedDate to 2023-07-08') 586 .margin(10) 587 .onClick(() => { 588 AppStorage.setOrCreate("date", new Date('2023-07-08')); 589 }) 590 Button('increase the year by 1') 591 .margin(10) 592 .onClick(() => { 593 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 594 }) 595 Button('increase the month by 1') 596 .margin(10) 597 .onClick(() => { 598 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1); 599 }) 600 Button('increase the day by 1') 601 .margin(10) 602 .onClick(() => { 603 this.selectedDate.setDate(this.selectedDate.getDate() + 1); 604 }) 605 DatePicker({ 606 start: new Date('1970-1-1'), 607 end: new Date('2100-1-1'), 608 selected: $$this.selectedDate 609 }) 610 }.width('100%') 611 } 612} 613``` 614 615 616### 装饰Map类型变量 617 618> **说明:** 619> 620> 从API version 12开始,AppStorage支持Map类型。 621 622在下面的示例中,@StorageLink装饰的message类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。 623 624```ts 625@Entry 626@Component 627struct MapSample { 628 @StorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 629 630 build() { 631 Row() { 632 Column() { 633 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 634 Text(`${item[0]}`).fontSize(30) 635 Text(`${item[1]}`).fontSize(30) 636 Divider() 637 }) 638 Button('init map').onClick(() => { 639 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]); 640 }) 641 Button('set new one').onClick(() => { 642 this.message.set(4, "d"); 643 }) 644 Button('clear').onClick(() => { 645 this.message.clear(); 646 }) 647 Button('replace the existing one').onClick(() => { 648 this.message.set(0, "aa"); 649 }) 650 Button('delete the existing one').onClick(() => { 651 AppStorage.get<Map<number, string>>("map")?.delete(0); 652 }) 653 } 654 .width('100%') 655 } 656 .height('100%') 657 } 658} 659``` 660 661 662### 装饰Set类型变量 663 664> **说明:** 665> 666> 从API version 12开始,AppStorage支持Set类型。 667 668在下面的示例中,@StorageLink装饰的memberSet类型为Set\<number\>,点击Button改变memberSet的值,视图会随之刷新。 669 670```ts 671@Entry 672@Component 673struct SetSample { 674 @StorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]); 675 676 build() { 677 Row() { 678 Column() { 679 ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => { 680 Text(`${item[0]}`) 681 .fontSize(30) 682 Divider() 683 }) 684 Button('init set') 685 .onClick(() => { 686 this.memberSet = new Set([0, 1, 2, 3, 4]); 687 }) 688 Button('set new one') 689 .onClick(() => { 690 AppStorage.get<Set<number>>("set")?.add(5); 691 }) 692 Button('clear') 693 .onClick(() => { 694 this.memberSet.clear(); 695 }) 696 Button('delete the first one') 697 .onClick(() => { 698 this.memberSet.delete(0); 699 }) 700 } 701 .width('100%') 702 } 703 .height('100%') 704 } 705} 706``` 707 708 709 710## 限制条件 711 712AppStorage与[PersistentStorage](arkts-persiststorage.md)以及[Environment](arkts-environment.md)配合使用时,需要注意以下几点: 713 714- 在AppStorage中创建属性后,调用PersistentStorage.persistProp()接口时,会使用在AppStorage中已经存在的值,并覆盖PersistentStorage中的同名属性,所以建议要使用相反的调用顺序,反例可见[在PersistentStorage之前访问AppStorage中的属性](arkts-persiststorage.md#在persistentstorage之前访问appstorage中的属性); 715 716- 如果在AppStorage中已经创建属性后,再调用Environment.envProp()创建同名的属性,会调用失败。因为AppStorage已经有同名属性,Environment环境变量不会再写入AppStorage中,所以建议AppStorage中属性不要使用Environment预置环境变量名。 717 718- 状态装饰器装饰的变量,改变会引起UI的渲染更新,如果改变的变量不是用于UI更新,只是用于消息传递,推荐使用 emitter方式。例子可见<!--Del-->[<!--DelEnd-->不建议借助@StorageLink的双向同步机制实现事件通知<!--Del-->](#不建议借助storagelink的双向同步机制实现事件通知)<!--DelEnd-->。 719<!--no_check--> 720 721## 常见问题 722 723### \@StorageProp本地更改值后,无法通过AppStorage接口更新 724 725```ts 726AppStorage.setOrCreate('PropA', false); 727 728@Entry 729@Component 730struct Index { 731 @StorageProp('PropA') @Watch('onChange') propA: boolean = false; 732 733 onChange() { 734 console.log(`propA change`); 735 } 736 737 aboutToAppear(): void { 738 this.propA = true; 739 } 740 741 build() { 742 Column() { 743 Text(`${this.propA}`) 744 Button('change') 745 .onClick(() => { 746 AppStorage.setOrCreate('PropA', false); 747 console.log(`PropA: ${this.propA}`); 748 }) 749 } 750 } 751} 752``` 753 754上述示例,在点击事件之前,PropA的值已经在本地被更改为true,而AppStorage中存的值仍为false。当点击事件通过setOrCreate接口尝试更新PropA的值为false时,由于AppStorage中的值为false,两者相等,不会触发更新同步,因此@StorageProp的值仍为true。 755 756如果想要实现二者同步,有两种方式: 757(1)将\@StorageProp更改为\@StorageLink。 758(2)本地更改值的方式变为使用AppStorage.setOrCreate('PropA', true)的方式。 759