• 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
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类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现-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![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
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