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![zh-cn_image_0000001552978157](figures/zh-cn_image_0000001552978157.png)
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![zh-cn_image_0000001501938718](figures/zh-cn_image_0000001501938718.png)
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