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