• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![zh-cn_image_0000001552978157](figures/zh-cn_image_0000001552978157.png)
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![zh-cn_image_0000001501938718](figures/zh-cn_image_0000001501938718.png)
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