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类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现-1)。<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 165 AppStorage.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 1844. 状态装饰器装饰的变量,改变会引起UI的渲染更新,如果改变的变量不是用于UI更新,只是用于消息传递,推荐使用emitter方式。例子可见<!--Del-->[<!--DelEnd-->不建议借助@StorageLink的双向同步机制实现事件通知<!--Del-->](#不建议借助storagelink的双向同步机制实现事件通知)<!--DelEnd-->。 185 1865. AppStorage同一进程内共享,UIAbility和<!--Del-->[<!--DelEnd-->UIExtensionAbility<!--Del-->](../../application-models/uiextensionability.md)<!--DelEnd-->是两个进程,所以在<!--Del-->[<!--DelEnd-->UIExtensionAbility<!--Del-->](../../application-models/uiextensionability.md)<!--DelEnd-->中不共享主进程的AppStorage。 187 188 189## 使用场景 190 191 192### 从应用逻辑使用AppStorage和LocalStorage 193 194AppStorage是单例,它的所有API都是静态的,使用方法类似于LocalStorage中对应的非静态方法。 195 196 197```ts 198AppStorage.setOrCreate('PropA', 47); 199 200let storage: LocalStorage = new LocalStorage(); 201storage.setOrCreate('PropA',17); 202let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17 203let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47 204let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47 205let prop: SubscribedAbstractProperty<number> = AppStorage.prop('PropA'); // prop.get() == 47 206 207link1.set(48); // 双向同步: link1.get() == link2.get() == prop.get() == 48 208prop.set(1); // 单向同步: prop.get() == 1; 但 link1.get() == link2.get() == 48 209link1.set(49); // 双向同步: link1.get() == link2.get() == prop.get() == 49 210 211storage.get<number>('PropA') // == 17 212storage.set('PropA', 101); 213storage.get<number>('PropA') // == 101 214 215AppStorage.get<number>('PropA') // == 49 216link1.get() // == 49 217link2.get() // == 49 218prop.get() // == 49 219``` 220 221 222### 从UI内部使用AppStorage和LocalStorage 223 224\@StorageLink变量装饰器与AppStorage配合使用,正如\@LocalStorageLink与LocalStorage配合使用一样。此装饰器使用AppStorage中的属性创建双向数据同步。 225 226 227```ts 228class Data { 229 code: number; 230 231 constructor(code: number) { 232 this.code = code; 233 } 234} 235 236AppStorage.setOrCreate('PropA', 47); 237AppStorage.setOrCreate('PropB', new Data(50)); 238let storage = new LocalStorage(); 239storage.setOrCreate('LinkA', 48); 240storage.setOrCreate('LinkB', new Data(100)); 241 242@Entry(storage) 243@Component 244struct Index { 245 @StorageLink('PropA') storageLink: number = 1; 246 @LocalStorageLink('LinkA') localStorageLink: number = 1; 247 @StorageLink('PropB') storageLinkObject: Data = new Data(1); 248 @LocalStorageLink('LinkB') localStorageLinkObject: Data = new Data(1); 249 250 build() { 251 Column({ space: 20 }) { 252 Text(`From AppStorage ${this.storageLink}`) 253 .onClick(() => { 254 this.storageLink += 1; 255 }) 256 257 Text(`From LocalStorage ${this.localStorageLink}`) 258 .onClick(() => { 259 this.localStorageLink += 1; 260 }) 261 262 Text(`From AppStorage ${this.storageLinkObject.code}`) 263 .onClick(() => { 264 this.storageLinkObject.code += 1; 265 }) 266 267 Text(`From LocalStorage ${this.localStorageLinkObject.code}`) 268 .onClick(() => { 269 this.localStorageLinkObject.code += 1; 270 }) 271 } 272 } 273} 274``` 275 276### 不建议借助@StorageLink的双向同步机制实现事件通知 277 278不建议开发者使用@StorageLink和AppStorage的双向同步的机制来实现事件通知,因为AppStorage中的变量可能绑定在多个不同页面的组件中,但事件通知则不一定需要通知到所有的这些组件。并且,当这些@StorageLink装饰的变量在UI中使用时,会触发UI刷新,带来不必要的性能影响。 279 280示例代码中,TapImage中的点击事件,会触发AppStorage中tapIndex对应属性的改变。因为@StorageLink是双向同步,修改会同步回AppStorage中,所以,所有绑定AppStorage的tapIndex自定义组件里都能感知到tapIndex的变化。使用@Watch监听到tapIndex的变化后,修改状态变量tapColor从而触发UI刷新(此处tapIndex并未直接绑定在UI上,因此tapIndex的变化不会直接触发UI刷新)。 281 282使用该机制来实现事件通知需要确保AppStorage中的变量尽量不要直接绑定在UI上,且需要控制@Watch函数的复杂度(如果@Watch函数执行时间长,会影响UI刷新效率)。 283 284 285```ts 286// xxx.ets 287class ViewData { 288 title: string; 289 uri: Resource; 290 color: Color = Color.Black; 291 292 constructor(title: string, uri: Resource) { 293 this.title = title; 294 this.uri = uri 295 } 296} 297 298@Entry 299@Component 300struct Gallery { 301 // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 302 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 303 scroller: Scroller = new Scroller() 304 305 build() { 306 Column() { 307 Grid(this.scroller) { 308 ForEach(this.dataList, (item: ViewData, index?: number) => { 309 GridItem() { 310 TapImage({ 311 uri: item.uri, 312 index: index 313 }) 314 }.aspectRatio(1) 315 316 }, (item: ViewData, index?: number) => { 317 return JSON.stringify(item) + index; 318 }) 319 }.columnsTemplate('1fr 1fr') 320 } 321 322 } 323} 324 325@Component 326export struct TapImage { 327 @StorageLink('tapIndex') @Watch('onTapIndexChange') tapIndex: number = -1; 328 @State tapColor: Color = Color.Black; 329 private index: number = 0; 330 private uri: Resource = { 331 id: 0, 332 type: 0, 333 moduleName: "", 334 bundleName: "" 335 }; 336 337 // 判断是否被选中 338 onTapIndexChange() { 339 if (this.tapIndex >= 0 && this.index === this.tapIndex) { 340 console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, red`) 341 this.tapColor = Color.Red; 342 } else { 343 console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, black`) 344 this.tapColor = Color.Black; 345 } 346 } 347 348 build() { 349 Column() { 350 Image(this.uri) 351 .objectFit(ImageFit.Cover) 352 .onClick(() => { 353 this.tapIndex = this.index; 354 }) 355 .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor }) 356 } 357 358 } 359} 360``` 361 362相比借助@StorageLink的双向同步机制实现事件通知,开发者可以使用emit订阅某个事件并接收事件回调的方式来减少开销,增强代码的可读性。 363 364> **说明:** 365> 366> emit接口不支持在Previewer预览器中使用。 367 368 369```ts 370// xxx.ets 371import { emitter } from '@kit.BasicServicesKit'; 372 373let NextID: number = 0; 374 375class ViewData { 376 title: string; 377 uri: Resource; 378 color: Color = Color.Black; 379 id: number; 380 381 constructor(title: string, uri: Resource) { 382 this.title = title; 383 this.uri = uri 384 this.id = NextID++; 385 } 386} 387 388@Entry 389@Component 390struct Gallery { 391 // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 392 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 393 scroller: Scroller = new Scroller() 394 private preIndex: number = -1 395 396 build() { 397 Column() { 398 Grid(this.scroller) { 399 ForEach(this.dataList, (item: ViewData) => { 400 GridItem() { 401 TapImage({ 402 uri: item.uri, 403 index: item.id 404 }) 405 }.aspectRatio(1) 406 .onClick(() => { 407 if (this.preIndex === item.id) { 408 return 409 } 410 let innerEvent: emitter.InnerEvent = { eventId: item.id } 411 // 选中态:黑变红 412 let eventData: emitter.EventData = { 413 data: { 414 "colorTag": 1 415 } 416 } 417 emitter.emit(innerEvent, eventData) 418 419 if (this.preIndex != -1) { 420 console.info(`preIndex: ${this.preIndex}, index: ${item.id}, black`) 421 let innerEvent: emitter.InnerEvent = { eventId: this.preIndex } 422 // 取消选中态:红变黑 423 let eventData: emitter.EventData = { 424 data: { 425 "colorTag": 0 426 } 427 } 428 emitter.emit(innerEvent, eventData) 429 } 430 this.preIndex = item.id 431 }) 432 }, (item: ViewData) => JSON.stringify(item)) 433 }.columnsTemplate('1fr 1fr') 434 } 435 436 } 437} 438 439@Component 440export struct TapImage { 441 @State tapColor: Color = Color.Black; 442 private index: number = 0; 443 private uri: Resource = { 444 id: 0, 445 type: 0, 446 moduleName: "", 447 bundleName: "" 448 }; 449 450 onTapIndexChange(colorTag: emitter.EventData) { 451 if (colorTag.data != null) { 452 this.tapColor = colorTag.data.colorTag ? Color.Red : Color.Black 453 } 454 } 455 456 aboutToAppear() { 457 //定义事件ID 458 let innerEvent: emitter.InnerEvent = { eventId: this.index } 459 emitter.on(innerEvent, data => { 460 this.onTapIndexChange(data) 461 }) 462 } 463 464 build() { 465 Column() { 466 Image(this.uri) 467 .objectFit(ImageFit.Cover) 468 .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor }) 469 } 470 } 471} 472``` 473 474以上通知事件逻辑简单,也可以简化成三元表达式。 475 476```ts 477// xxx.ets 478class ViewData { 479 title: string; 480 uri: Resource; 481 color: Color = Color.Black; 482 483 constructor(title: string, uri: Resource) { 484 this.title = title; 485 this.uri = uri 486 } 487} 488 489@Entry 490@Component 491struct Gallery { 492 // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 493 dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))] 494 scroller: Scroller = new Scroller() 495 496 build() { 497 Column() { 498 Grid(this.scroller) { 499 ForEach(this.dataList, (item: ViewData, index?: number) => { 500 GridItem() { 501 TapImage({ 502 uri: item.uri, 503 index: index 504 }) 505 }.aspectRatio(1) 506 507 }, (item: ViewData, index?: number) => { 508 return JSON.stringify(item) + index; 509 }) 510 }.columnsTemplate('1fr 1fr') 511 } 512 513 } 514} 515 516@Component 517export struct TapImage { 518 @StorageLink('tapIndex') tapIndex: number = -1; 519 private index: number = 0; 520 private uri: Resource = { 521 id: 0, 522 type: 0, 523 moduleName: "", 524 bundleName: "" 525 }; 526 527 build() { 528 Column() { 529 Image(this.uri) 530 .objectFit(ImageFit.Cover) 531 .onClick(() => { 532 this.tapIndex = this.index; 533 }) 534 .border({ 535 width: 5, 536 style: BorderStyle.Dotted, 537 color: (this.tapIndex >= 0 && this.index === this.tapIndex) ? Color.Red : Color.Black 538 }) 539 } 540 } 541} 542``` 543 544 545### AppStorage支持联合类型 546 547在下面的示例中,变量A的类型为number | null,变量B的类型为number | undefined。Text组件初始化分别显示为null和undefined,点击切换为数字,再次点击切换回null和undefined。 548 549```ts 550@Component 551struct StorLink { 552 @StorageLink("LinkA") LinkA: number | null = null; 553 @StorageLink("LinkB") LinkB: number | undefined = undefined; 554 555 build() { 556 Column() { 557 Text("@StorageLink接口初始化,@StorageLink取值") 558 Text(this.LinkA + "").fontSize(20).onClick(() => { 559 this.LinkA ? this.LinkA = null : this.LinkA = 1; 560 }) 561 Text(this.LinkB + "").fontSize(20).onClick(() => { 562 this.LinkB ? this.LinkB = undefined : this.LinkB = 1; 563 }) 564 } 565 .borderWidth(3).borderColor(Color.Red) 566 567 } 568} 569 570@Component 571struct StorProp { 572 @StorageProp("PropA") PropA: number | null = null; 573 @StorageProp("PropB") PropB: number | undefined = undefined; 574 575 build() { 576 Column() { 577 Text("@StorageProp接口初始化,@StorageProp取值") 578 Text(this.PropA + "").fontSize(20).onClick(() => { 579 this.PropA ? this.PropA = null : this.PropA = 1; 580 }) 581 Text(this.PropB + "").fontSize(20).onClick(() => { 582 this.PropB ? this.PropB = undefined : this.PropB = 1; 583 }) 584 } 585 .borderWidth(3).borderColor(Color.Blue) 586 } 587} 588 589@Entry 590@Component 591struct Index { 592 build() { 593 Row() { 594 Column() { 595 StorLink() 596 StorProp() 597 } 598 .width('100%') 599 } 600 .height('100%') 601 } 602} 603``` 604 605 606### 装饰Date类型变量 607 608> **说明:** 609> 610> 从API version 12开始,AppStorage支持Date类型。 611 612在下面的示例中,@StorageLink装饰的selectedDate类型为Date,点击Button改变selectedDate的值,视图会随之刷新。 613 614```ts 615@Entry 616@Component 617struct DateSample { 618 @StorageLink("date") selectedDate: Date = new Date('2021-08-08'); 619 620 build() { 621 Column() { 622 Button('set selectedDate to 2023-07-08') 623 .margin(10) 624 .onClick(() => { 625 AppStorage.setOrCreate("date", new Date('2023-07-08')); 626 }) 627 Button('increase the year by 1') 628 .margin(10) 629 .onClick(() => { 630 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 631 }) 632 Button('increase the month by 1') 633 .margin(10) 634 .onClick(() => { 635 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1); 636 }) 637 Button('increase the day by 1') 638 .margin(10) 639 .onClick(() => { 640 this.selectedDate.setDate(this.selectedDate.getDate() + 1); 641 }) 642 DatePicker({ 643 start: new Date('1970-1-1'), 644 end: new Date('2100-1-1'), 645 selected: $$this.selectedDate 646 }) 647 }.width('100%') 648 } 649} 650``` 651 652 653### 装饰Map类型变量 654 655> **说明:** 656> 657> 从API version 12开始,AppStorage支持Map类型。 658 659在下面的示例中,@StorageLink装饰的message类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。 660 661```ts 662@Entry 663@Component 664struct MapSample { 665 @StorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 666 667 build() { 668 Row() { 669 Column() { 670 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 671 Text(`${item[0]}`).fontSize(30) 672 Text(`${item[1]}`).fontSize(30) 673 Divider() 674 }) 675 Button('init map').onClick(() => { 676 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]); 677 }) 678 Button('set new one').onClick(() => { 679 this.message.set(4, "d"); 680 }) 681 Button('clear').onClick(() => { 682 this.message.clear(); 683 }) 684 Button('replace the existing one').onClick(() => { 685 this.message.set(0, "aa"); 686 }) 687 Button('delete the existing one').onClick(() => { 688 AppStorage.get<Map<number, string>>("map")?.delete(0); 689 }) 690 } 691 .width('100%') 692 } 693 .height('100%') 694 } 695} 696``` 697 698 699### 装饰Set类型变量 700 701> **说明:** 702> 703> 从API version 12开始,AppStorage支持Set类型。 704 705在下面的示例中,@StorageLink装饰的memberSet类型为Set\<number\>,点击Button改变memberSet的值,视图会随之刷新。 706 707```ts 708@Entry 709@Component 710struct SetSample { 711 @StorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]); 712 713 build() { 714 Row() { 715 Column() { 716 ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => { 717 Text(`${item[0]}`) 718 .fontSize(30) 719 Divider() 720 }) 721 Button('init set') 722 .onClick(() => { 723 this.memberSet = new Set([0, 1, 2, 3, 4]); 724 }) 725 Button('set new one') 726 .onClick(() => { 727 AppStorage.get<Set<number>>("set")?.add(5); 728 }) 729 Button('clear') 730 .onClick(() => { 731 this.memberSet.clear(); 732 }) 733 Button('delete the first one') 734 .onClick(() => { 735 this.memberSet.delete(0); 736 }) 737 } 738 .width('100%') 739 } 740 .height('100%') 741 } 742} 743``` 744 745## 常见问题 746 747### \@StorageProp本地更改值后,无法通过AppStorage接口更新 748 749```ts 750AppStorage.setOrCreate('PropA', false); 751 752@Entry 753@Component 754struct Index { 755 @StorageProp('PropA') @Watch('onChange') propA: boolean = false; 756 757 onChange() { 758 console.log(`propA change`); 759 } 760 761 aboutToAppear(): void { 762 this.propA = true; 763 } 764 765 build() { 766 Column() { 767 Text(`${this.propA}`) 768 Button('change') 769 .onClick(() => { 770 AppStorage.setOrCreate('PropA', false); 771 console.log(`PropA: ${this.propA}`); 772 }) 773 } 774 } 775} 776``` 777 778上述示例,在点击事件之前,PropA的值已经在本地被更改为true,而AppStorage中存的值仍为false。当点击事件通过setOrCreate接口尝试更新PropA的值为false时,由于AppStorage中的值为false,两者相等,不会触发更新同步,因此@StorageProp的值仍为true。 779 780如果想要实现二者同步,有两种方式: 781(1)将\@StorageProp更改为\@StorageLink。 782(2)本地更改值的方式变为使用AppStorage.setOrCreate('PropA', true)的方式。 783