1# LocalStorage:页面级UI状态存储
2
3
4LocalStorage是页面级的UI状态存储,通过\@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage支持UIAbility实例内多个页面间状态共享。
5
6
7本文仅介绍LocalStorage使用场景和相关的装饰器:\@LocalStorageProp和\@LocalStorageLink。
8
9
10在阅读本文档前,建议开发者对状态管理框架有基本的了解。建议提前阅读:[状态管理概述](./arkts-state-management-overview.md)。
11
12LocalStorage还提供了API接口,可以让开发者通过接口在自定义组件外手动触发Storage对应key的增删改查,建议配合[LocalStorage API文档](../reference/apis-arkui/arkui-ts/ts-state-management.md#localstorage9)阅读。
13
14> **说明:**
15>
16> LocalStorage从API version 9开始支持。
17
18
19## 概述
20
21LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内的“数据库”。
22
23- 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过getShared接口,实现跨页面、UIAbility实例内共享。
24
25- 组件树的根节点,即被\@Entry装饰的\@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限。
26
27- \@Component装饰的组件既可以自动继承来自父组件的LocalStorage实例,也可以传入指定的LocalStorage的实例,详见:[自定义组件接收LocalStorage实例](#自定义组件接收localstorage实例)。
28
29- LocalStorage中的所有属性都是可变的。
30
31应用程序决定LocalStorage对象的生命周期。当应用释放最后一个指向LocalStorage的引用时,比如销毁最后一个自定义组件,LocalStorage将被JS Engine垃圾回收。
32
33LocalStorage根据与\@Component装饰的组件的同步类型不同,提供了两个装饰器:
34
35- [@LocalStorageProp](#localstorageprop):\@LocalStorageProp装饰的变量与LocalStorage中给定属性建立单向同步关系。
36
37- [@LocalStorageLink](#localstoragelink):\@LocalStorageLink装饰的变量与LocalStorage中给定属性建立双向同步关系。
38
39
40## \@LocalStorageProp
41
42在上文中已经提到,如果要建立LocalStorage和自定义组件的联系,需要使用\@LocalStorageProp和\@LocalStorageLink装饰器。使用\@LocalStorageProp(key)/\@LocalStorageLink(key)装饰组件内的变量,key标识了LocalStorage的属性。
43
44
45当自定义组件初始化的时候,\@LocalStorageProp(key)/\@LocalStorageLink(key)装饰的变量会通过给定的key,绑定LocalStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证LocalStorage一定存在给定的key(这取决于应用逻辑是否在组件初始化之前在LocalStorage实例中存入对应的属性)。
46
47
48> **说明:**
49>
50> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。
51>
52> 从API version 11开始,该装饰器支持在原子化服务中使用。
53
54\@LocalStorageProp(key)是和LocalStorage中key对应的属性建立单向数据同步,ArkUI框架支持修改@LocalStorageProp(key)在本地的值,但是对本地值的修改不会同步回LocalStorage中。相反,如果LocalStorage中key对应的属性值发生改变,例如通过set接口对LocalStorage中的值进行修改,改变会同步给\@LocalStorageProp(key),并覆盖掉本地的值。
55
56
57### 装饰器使用规则说明
58
59| \@LocalStorageProp变量装饰器 | 说明                                       |
60| ----------------------- | ---------------------------------------- |
61| 装饰器参数                   | key:常量字符串,必填(字符串需要有引号)。                  |
62| 允许装饰的变量类型               | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。<br/>类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。<br/>不支持any,API12及以上支持undefined和null类型。<br/>API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[LocalStorage支持联合类型](#localstorage支持联合类型)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@LocalStorageProp("AA") a: number \| null = null`是推荐的,不推荐`@LocalStorageProp("AA") a: number = null`。 |
63| 同步类型                    | 单向同步:从LocalStorage的对应属性到组件的状态变量。组件本地的修改是允许的,但是LocalStorage中给定的属性一旦发生变化,将覆盖本地的修改。 |
64| 被装饰变量的初始值               | 必须指定,如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。 |
65
66
67### 变量的传递/访问规则说明
68
69| 传递/访问      | 说明                                       |
70| ---------- | ---------------------------------------- |
71| 从父节点初始化和更新 | 禁止,\@LocalStorageProp不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化。 |
72| 初始化子节点     | 支持,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
73| 是否支持组件外访问  | 否。                                       |
74
75  **图1** \@LocalStorageProp初始化规则图示  
76
77![zh-cn_image_0000001501936014](figures/zh-cn_image_0000001501936014.png)
78
79
80### 观察变化和行为表现
81
82**观察变化**
83
84
85- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
86
87- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用localstorage](#从ui内部使用localstorage))。
88
89- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
90
91- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。
92
93- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
94
95- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
96
97
98**框架行为**
99
100
101- 被\@LocalStorageProp装饰的变量的值的变化不会同步回LocalStorage里。
102
103- \@LocalStorageProp装饰的变量变化会使当前自定义组件中关联的组件刷新。
104
105- LocalStorage(key)中值的变化会引发所有被\@LocalStorageProp对应key装饰的变量的变化,会覆盖\@LocalStorageProp本地的改变。
106
107![LocalStorageProp_framework_behavior](figures/LocalStorageProp_framework_behavior.png)
108
109
110## \@LocalStorageLink
111
112> **说明:**
113>
114> 从API version 11开始,该装饰器支持在原子化服务中使用。
115
116如果我们需要将自定义组件的状态变量的更新同步回LocalStorage,就需要用到\@LocalStorageLink。
117
118\@LocalStorageLink(key)是和LocalStorage中key对应的属性建立双向数据同步:
119
1201. 本地修改发生,该修改会被写回LocalStorage中;
121
1222. LocalStorage中的修改发生后,该修改会被同步到所有绑定LocalStorage对应key的属性上,包括单向(\@LocalStorageProp和通过prop创建的单向绑定变量)、双向(\@LocalStorageLink和通过link创建的双向绑定变量)变量。
123
124### 装饰器使用规则说明
125
126| \@LocalStorageLink变量装饰器 | 说明                                       |
127| ----------------------- | ---------------------------------------- |
128| 装饰器参数                   | key:常量字符串,必填(字符串需要有引号)。                  |
129| 允许装饰的变量类型               | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。<br/>类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。<br/>不支持any,API12及以上支持undefined和null类型。<br/>API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[LocalStorage支持联合类型](#localstorage支持联合类型)。 <br/>**注意**<br/>当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@LocalStorageLink("AA") a: number \| null = null`是推荐的,不推荐`@LocalStorageLink("AA") a: number = null`。 |
130| 同步类型                    | 双向同步:从LocalStorage的对应属性到自定义组件,从自定义组件到LocalStorage对应属性。 |
131| 被装饰变量的初始值               | 必须指定,如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。 |
132
133
134### 变量的传递/访问规则说明
135
136| 传递/访问      | 说明                                       |
137| ---------- | ---------------------------------------- |
138| 从父节点初始化和更新 | 禁止,\@LocalStorageLink不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化。 |
139| 初始化子节点     | 支持,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
140| 是否支持组件外访问  | 否。                                       |
141
142
143  **图2** \@LocalStorageLink初始化规则图示  
144
145
146![zh-cn_image_0000001552855957](figures/zh-cn_image_0000001552855957.png)
147
148
149### 观察变化和行为表现
150
151**观察变化**
152
153
154- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
155
156- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用localstorage](#从ui内部使用localstorage))。
157
158- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
159
160- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。
161
162- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
163
164- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
165
166
167**框架行为**
168
169
1701. 当\@LocalStorageLink(key)装饰的数值改变被观察到时,修改将被同步回LocalStorage对应属性键值key的属性中。
171
1722. LocalStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向\@LocalStorageLink和单向\@LocalStorageProp)都将同步修改。
173
1743. 当\@LocalStorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回LocalStorage中,还会引起所属的自定义组件的重新渲染。
175
176![LocalStorageLink_framework_behavior](figures/LocalStorageLink_framework_behavior.png)
177
178
179## 限制条件
180
1811. \@LocalStorageProp/\@LocalStorageLink的参数必须为string类型,否则编译期会报错。
182
183```ts
184let storage = new LocalStorage();
185storage.setOrCreate('PropA', 48);
186
187// 错误写法,编译报错
188@LocalStorageProp() localStorageProp: number = 1;
189@LocalStorageLink() localStorageLink: number = 2;
190
191// 正确写法
192@LocalStorageProp('PropA') localStorageProp: number = 1;
193@LocalStorageLink('PropA') localStorageLink: number = 2;
194```
195
1962. \@StorageProp与\@StorageLink不支持装饰Function类型的变量,框架会抛出运行时错误。
197
1983. LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。
199
2004. LocalStorage是页面级存储,[getShared](../reference/apis-arkui/arkui-ts/ts-state-management.md#getshared10)接口仅能获取当前Stage通过[windowStage.loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9)传入的LocalStorage实例,否则返回undefined。例子可见[将LocalStorage实例从UIAbility共享到一个或多个视图](#将localstorage实例从uiability共享到一个或多个视图)。
201
202
203## 使用场景
204
205
206### 应用逻辑使用LocalStorage
207
208
209```ts
210let para: Record<string,number> = { 'PropA': 47 };
211let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化
212let propA: number | undefined = storage.get('PropA'); // propA == 47
213let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47
214let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47
215let prop: SubscribedAbstractProperty<number> = storage.prop('PropA'); // prop.get() == 47
216link1.set(48); // 双向同步: link1.get() == link2.get() == prop.get() == 48
217prop.set(1); // 单向同步: prop.get() == 1; 但 link1.get() == link2.get() == 48
218link1.set(49); // 双向同步: link1.get() == link2.get() == prop.get() == 49
219```
220
221
222### 从UI内部使用LocalStorage
223
224除了应用程序逻辑使用LocalStorage,还可以借助LocalStorage相关的两个装饰器\@LocalStorageProp和\@LocalStorageLink,在UI组件内部获取到LocalStorage实例中存储的状态变量。
225
226本示例以\@LocalStorageLink为例,展示了:
227
228- 使用构造函数创建LocalStorage实例storage;
229
230- 使用\@Entry装饰器将storage添加到Parent顶层组件中;
231
232- \@LocalStorageLink绑定LocalStorage对给定的属性,建立双向数据同步。
233
234 ```ts
235class Data {
236  code: number;
237
238  constructor(code: number) {
239    this.code = code;
240  }
241}
242// 创建新实例并使用给定对象初始化
243let para: Record<string, number> = { 'PropA': 47 };
244let storage: LocalStorage = new LocalStorage(para);
245storage.setOrCreate('PropB', new Data(50));
246
247@Component
248struct Child {
249  // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
250  @LocalStorageLink('PropA') childLinkNumber: number = 1;
251  // @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
252  @LocalStorageLink('PropB') childLinkObject: Data = new Data(0);
253
254  build() {
255    Column({ space: 15 }) {
256      Button(`Child from LocalStorage ${this.childLinkNumber}`) // 更改将同步至LocalStorage中的'PropA'以及Parent.parentLinkNumber
257        .onClick(() => {
258          this.childLinkNumber += 1;
259        })
260
261      Button(`Child from LocalStorage ${this.childLinkObject.code}`) // 更改将同步至LocalStorage中的'PropB'以及Parent.parentLinkObject.code
262        .onClick(() => {
263          this.childLinkObject.code += 1;
264        })
265    }
266  }
267}
268// 使LocalStorage可从@Component组件访问
269@Entry(storage)
270@Component
271struct Parent {
272  // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
273  @LocalStorageLink('PropA') parentLinkNumber: number = 1;
274  // @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
275  @LocalStorageLink('PropB') parentLinkObject: Data = new Data(0);
276
277  build() {
278    Column({ space: 15 }) {
279      Button(`Parent from LocalStorage ${this.parentLinkNumber}`) // 由于LocalStorage中PropA已经被初始化,因此this.parentLinkNumber的值为47
280        .onClick(() => {
281          this.parentLinkNumber += 1;
282        })
283
284      Button(`Parent from LocalStorage ${this.parentLinkObject.code}`) // 由于LocalStorage中PropB已经被初始化,因此this.parentLinkObject.code的值为50
285        .onClick(() => {
286          this.parentLinkObject.code += 1;
287        })
288      // @Component子组件自动获得对Parent LocalStorage实例的访问权限。
289      Child()
290    }
291  }
292}
293```
294
295
296### \@LocalStorageProp和LocalStorage单向同步的简单场景
297
298在下面的示例中,Parent 组件和Child组件分别在本地创建了与storage的'PropA'对应属性的单向同步的数据,我们可以看到:
299
300- Parent中对this.storageProp1的修改,只会在Parent中生效,并没有同步回storage;
301
302- Child组件中,Text绑定的storageProp2 依旧显示47。
303
304```ts
305// 创建新实例并使用给定对象初始化
306let para: Record<string, number> = { 'PropA': 47 };
307let storage: LocalStorage = new LocalStorage(para);
308// 使LocalStorage可从@Component组件访问
309@Entry(storage)
310@Component
311struct Parent {
312  // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
313  @LocalStorageProp('PropA') storageProp1: number = 1;
314
315  build() {
316    Column({ space: 15 }) {
317      // 点击后从47开始加1,只改变当前组件显示的storageProp1,不会同步到LocalStorage中
318      Button(`Parent from LocalStorage ${this.storageProp1}`)
319        .onClick(() => {
320          this.storageProp1 += 1;
321        })
322      Child()
323    }
324  }
325}
326
327@Component
328struct Child {
329  // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
330  @LocalStorageProp('PropA') storageProp2: number = 2;
331
332  build() {
333    Column({ space: 15 }) {
334      // 当Parent改变时,当前storageProp2不会改变,显示47
335      Text(`Parent from LocalStorage ${this.storageProp2}`)
336    }
337  }
338}
339```
340
341
342### \@LocalStorageLink和LocalStorage双向同步的简单场景
343
344下面的示例展示了\@LocalStorageLink装饰的数据和LocalStorage双向同步的场景:
345
346
347```ts
348// 构造LocalStorage实例
349let para: Record<string, number> = { 'PropA': 47 };
350let storage: LocalStorage = new LocalStorage(para);
351// 调用link(api9以上)接口构造'PropA'的双向同步数据,linkToPropA 是全局变量
352let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');
353
354@Entry(storage)
355@Component
356struct Parent {
357
358  // @LocalStorageLink('PropA')在Parent自定义组件中创建'PropA'的双向同步数据,初始值为47,因为在构造LocalStorage已经给“PropA”设置47
359  @LocalStorageLink('PropA') storageLink: number = 1;
360
361  build() {
362    Column() {
363      Text(`incr @LocalStorageLink variable`)
364        // 点击“incr @LocalStorageLink variable”,this.storageLink加1,改变同步回storage,全局变量linkToPropA也会同步改变
365
366        .onClick(() => {
367          this.storageLink += 1;
368        })
369
370      // 并不建议在组件内使用全局变量linkToPropA.get(),因为可能会有生命周期不同引起的错误。
371      Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`)
372    }
373  }
374}
375```
376
377
378### 兄弟组件之间同步状态变量
379
380下面的示例展示了通过\@LocalStorageLink双向同步兄弟组件之间的状态。
381
382先看Parent自定义组件中发生的变化:
383
3841. 点击“playCount ${this.playCount} dec by 1”,this.playCount减1,修改同步回LocalStorage中,Child组件中的playCountLink绑定的组件会同步刷新;
385
3862. 点击“countStorage ${this.playCount} incr by 1”,调用LocalStorage的set接口,更新LocalStorage中“countStorage”对应的属性,Child组件中的playCountLink绑定的组件会同步刷新;
387
3883. Text组件“playCount in LocalStorage for debug ${storage.get&lt;number&gt;('countStorage')}”没有同步刷新,因为storage.get&lt;number&gt;('countStorage')返回的是常规变量,常规变量的更新并不会引起Text组件的重新渲染。
389
390Child自定义组件中的变化:
391
3921. playCountLink的刷新会同步回LocalStorage,并且引起兄弟组件和父组件相应的刷新。
393
394```ts
395let count: Record<string, number> = { 'countStorage': 1 };
396let storage: LocalStorage = new LocalStorage(count);
397
398@Component
399struct Child {
400  // 子组件实例的名字
401  label: string = 'no name';
402  // 和LocalStorage中“countStorage”的双向绑定数据
403  @LocalStorageLink('countStorage') playCountLink: number = 0;
404
405  build() {
406    Row() {
407      Text(this.label)
408        .width(50).height(60).fontSize(12)
409      Text(`playCountLink ${this.playCountLink}: inc by 1`)
410        .onClick(() => {
411          this.playCountLink += 1;
412        })
413        .width(200).height(60).fontSize(12)
414    }.width(300).height(60)
415  }
416}
417
418@Entry(storage)
419@Component
420struct Parent {
421  @LocalStorageLink('countStorage') playCount: number = 0;
422
423  build() {
424    Column() {
425      Row() {
426        Text('Parent')
427          .width(50).height(60).fontSize(12)
428        Text(`playCount ${this.playCount} dec by 1`)
429          .onClick(() => {
430            this.playCount -= 1;
431          })
432          .width(250).height(60).fontSize(12)
433      }.width(300).height(60)
434
435      Row() {
436        Text('LocalStorage')
437          .width(50).height(60).fontSize(12)
438        Text(`countStorage ${this.playCount} incr by 1`)
439          .onClick(() => {
440            storage.set<number | undefined>('countStorage', Number(storage.get<number>('countStorage')) + 1);
441          })
442          .width(250).height(60).fontSize(12)
443      }.width(300).height(60)
444
445      Child({ label: 'ChildA' })
446      Child({ label: 'ChildB' })
447
448      Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`)
449        .width(300).height(60).fontSize(12)
450    }
451  }
452}
453```
454
455
456### 将LocalStorage实例从UIAbility共享到一个或多个视图
457
458上面的实例中,LocalStorage的实例仅仅在一个\@Entry装饰的组件和其所属的子组件(一个页面)中共享,如果希望其在多个视图中共享,可以在所属UIAbility中创建LocalStorage实例,并调用windowStage.[loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9)。
459
460
461```ts
462// EntryAbility.ets
463import { UIAbility } from '@kit.AbilityKit';
464import { window } from '@kit.ArkUI';
465
466export default class EntryAbility extends UIAbility {
467  para: Record<string, number> = {
468    'PropA': 47
469  };
470  storage: LocalStorage = new LocalStorage(this.para);
471
472  onWindowStageCreate(windowStage: window.WindowStage) {
473    windowStage.loadContent('pages/Index', this.storage);
474  }
475}
476```
477> **说明:**
478>
479> 在UI页面通过getShared接口获取通过loadContent共享的LocalStorage实例。
480>
481> LocalStorage.getShared()只在模拟器或者实机上才有效,在Previewer预览器中使用不生效。
482
483
484在下面的用例中,Index页面中的propA通过getShared()方法获取到共享的LocalStorage实例。点击Button跳转到Page页面,点击Change propA改变propA的值,back回Index页面后,页面中propA的值也同步修改。
485```ts
486// index.ets
487
488// 通过getShared接口获取stage共享的LocalStorage实例
489@Entry({ storage: LocalStorage.getShared() })
490@Component
491struct Index {
492  // 可以使用@LocalStorageLink/Prop与LocalStorage实例中的变量建立联系
493  @LocalStorageLink('PropA') propA: number = 1;
494  pageStack: NavPathStack = new NavPathStack();
495
496  build() {
497    Navigation(this.pageStack) {
498      Row(){
499        Column() {
500          Text(`${this.propA}`)
501            .fontSize(50)
502            .fontWeight(FontWeight.Bold)
503          Button("To Page")
504            .onClick(() => {
505              this.pageStack.pushPathByName('Page', null);
506            })
507        }
508        .width('100%')
509      }
510      .height('100%')
511    }
512  }
513}
514```
515
516```ts
517// Page.ets
518
519@Builder
520export function PageBuilder() {
521  Page()
522}
523
524// Page组件获得了父亲Index组件的LocalStorage实例
525@Component
526struct Page {
527  @LocalStorageLink('PropA') propA: number = 2;
528  pathStack: NavPathStack = new NavPathStack();
529
530  build() {
531    NavDestination() {
532      Row(){
533        Column() {
534          Text(`${this.propA}`)
535            .fontSize(50)
536            .fontWeight(FontWeight.Bold)
537
538          Button("Change propA")
539            .onClick(() => {
540              this.propA = 100;
541            })
542
543          Button("Back Index")
544            .onClick(() => {
545              this.pathStack.pop();
546            })
547        }
548        .width('100%')
549      }
550    }
551    .onReady((context: NavDestinationContext) => {
552      this.pathStack = context.pathStack;
553    })
554  }
555}
556```
557使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。
558```json
559{
560  "routerMap": [
561    {
562      "name": "Page",
563      "pageSourceFile": "src/main/ets/pages/Page.ets",
564      "buildFunction": "PageBuilder",
565      "data": {
566        "description" : "LocalStorage example"
567      }
568    }
569  ]
570}
571```
572
573> **说明:**
574>
575> 对于开发者更建议使用这个方式来构建LocalStorage的实例,并且在创建LocalStorage实例的时候就写入默认值,因为默认值可以作为运行异常的备份,也可以用作页面的单元测试。
576
577
578### 自定义组件接收LocalStorage实例
579
580除了根节点可通过@Entry来接收LocalStorage实例,自定义组件(子节点)也可以通过构造参数来传递LocalStorage实例。
581
582本示例以\@LocalStorageLink为例,展示了:
583
584- 父组件中的Text,显示LocalStorage实例localStorage1中PropA的值为“PropA”。
585
586- Child组件中,Text绑定的PropB,显示LocalStorage实例localStorage2中PropB的值为“PropB”。
587
588> **说明:**
589>
590> 从API version 12开始,自定义组件支持接收LocalStorage实例。
591> 当自定义组件作为子节点,定义了成员属性时,LocalStorage实例必须要放在第二个参数位置传递,否则会报类型不匹配的编译问题。
592> 当在自定义组件中定义了属性时,暂时不支持只有一个LocalStorage实例作为入参。如果没定义属性,可以只传入一个LocalStorage实例作为入参。
593> 如果定义的属性不需要从父组件初始化变量,则第一个参数需要传{}。
594> 作为构造参数传给子组件的LocalStorage实例在初始化时就会被决定,可以通过@LocalStorageLink或者LocalStorage的API修改LocalStorage实例中保存的属性值,但LocalStorage实例自身不能被动态修改。
595
596```ts
597let localStorage1: LocalStorage = new LocalStorage();
598localStorage1.setOrCreate('PropA', 'PropA');
599
600let localStorage2: LocalStorage = new LocalStorage();
601localStorage2.setOrCreate('PropB', 'PropB');
602
603@Entry(localStorage1)
604@Component
605struct Index {
606  // 'PropA',和localStorage1中'PropA'的双向同步
607  @LocalStorageLink('PropA') PropA: string = 'Hello World';
608  @State count: number = 0;
609
610  build() {
611    Row() {
612      Column() {
613        Text(this.PropA)
614          .fontSize(50)
615          .fontWeight(FontWeight.Bold)
616        // 使用LocalStorage 实例localStorage2
617        Child({ count: this.count }, localStorage2)
618      }
619      .width('100%')
620    }
621    .height('100%')
622  }
623}
624
625
626@Component
627struct Child {
628  @Link count: number;
629  //  'Hello World',和localStorage2中'PropB'的双向同步,如果localStorage2中没有'PropB',则使用默认值'Hello World'
630  @LocalStorageLink('PropB') PropB: string = 'Hello World';
631
632  build() {
633    Text(this.PropB)
634      .fontSize(50)
635      .fontWeight(FontWeight.Bold)
636  }
637}
638```
639
6401. 当自定义组件没有定义属性时,可以只传入一个LocalStorage实例作为入参。
641
642```ts
643let localStorage1: LocalStorage = new LocalStorage();
644localStorage1.setOrCreate('PropA', 'PropA');
645
646let localStorage2: LocalStorage = new LocalStorage();
647localStorage2.setOrCreate('PropB', 'PropB');
648
649@Entry(localStorage1)
650@Component
651struct Index {
652  // 'PropA',和localStorage1中'PropA'的双向同步
653  @LocalStorageLink('PropA') PropA: string = 'Hello World';
654  @State count: number = 0;
655
656  build() {
657    Row() {
658      Column() {
659        Text(this.PropA)
660          .fontSize(50)
661          .fontWeight(FontWeight.Bold)
662        // 使用LocalStorage 实例localStorage2
663        Child(localStorage2)
664      }
665      .width('100%')
666    }
667    .height('100%')
668  }
669}
670
671
672@Component
673struct Child {
674  build() {
675    Text("hello")
676      .fontSize(50)
677      .fontWeight(FontWeight.Bold)
678  }
679}
680```
681
6822. 当定义的属性不需要从父组件初始化变量时,第一个参数需要传{}。
683
684```ts
685let localStorage1: LocalStorage = new LocalStorage();
686localStorage1.setOrCreate('PropA', 'PropA');
687
688let localStorage2: LocalStorage = new LocalStorage();
689localStorage2.setOrCreate('PropB', 'PropB');
690
691@Entry(localStorage1)
692@Component
693struct Index {
694  // 'PropA',和localStorage1中'PropA'的双向同步
695  @LocalStorageLink('PropA') PropA: string = 'Hello World';
696  @State count: number = 0;
697
698  build() {
699    Row() {
700      Column() {
701        Text(this.PropA)
702          .fontSize(50)
703          .fontWeight(FontWeight.Bold)
704        // 使用LocalStorage 实例localStorage2
705        Child({}, localStorage2)
706      }
707      .width('100%')
708    }
709    .height('100%')
710  }
711}
712
713
714@Component
715struct Child {
716  @State count: number = 5;
717  // 'Hello World',和localStorage2中'PropB'的双向同步,如果localStorage2中没有'PropB',则使用默认值'Hello World'
718  @LocalStorageLink('PropB') PropB: string = 'Hello World';
719
720  build() {
721    Text(this.PropB)
722      .fontSize(50)
723      .fontWeight(FontWeight.Bold)
724  }
725}
726```
727
728
729### Navigation组件和LocalStorage联合使用
730
731可以通过传递不同的LocalStorage实例给自定义组件,从而实现在navigation跳转到不同的页面时,绑定不同的LocalStorage实例,显示对应绑定的值。
732
733本示例以\@LocalStorageLink为例,展示了:
734
735- 点击父组件中的Button "Next Page",创建并跳转到name为"pageOne"的子页面,Text显示信息为LocalStorage实例localStorageA中绑定的PropA的值,为"PropA"。
736
737- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageTwo"的子页面,Text显示信息为LocalStorage实例localStorageB中绑定的PropB的值,为"PropB"。
738
739- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageTree"的子页面,Text显示信息为LocalStorage实例localStorageC中绑定的PropC的值,为"PropC"。
740
741- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageOne"的子页面,Text显示信息为LocalStorage实例localStorageA中绑定的PropA的值,为"PropA"。
742
743- NavigationContentMsgStack自定义组件中的Text组件,共享对应自定义组件树上LocalStorage实例绑定的PropA的值。
744
745
746```ts
747let localStorageA: LocalStorage = new LocalStorage();
748localStorageA.setOrCreate('PropA', 'PropA');
749
750let localStorageB: LocalStorage = new LocalStorage();
751localStorageB.setOrCreate('PropB', 'PropB');
752
753let localStorageC: LocalStorage = new LocalStorage();
754localStorageC.setOrCreate('PropC', 'PropC');
755
756@Entry
757@Component
758struct MyNavigationTestStack {
759  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
760
761  @Builder
762  PageMap(name: string) {
763    if (name === 'pageOne') {
764      // 传递不同的LocalStorage实例
765      pageOneStack({}, localStorageA)
766    } else if (name === 'pageTwo') {
767      pageTwoStack({}, localStorageB)
768    } else if (name === 'pageThree') {
769      pageThreeStack({}, localStorageC)
770    }
771  }
772
773  build() {
774    Column({ space: 5 }) {
775      Navigation(this.pageInfo) {
776        Column() {
777          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
778            .width('80%')
779            .height(40)
780            .margin(20)
781            .onClick(() => {
782              this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈
783            })
784        }
785      }.title('NavIndex')
786      .navDestination(this.PageMap)
787      .mode(NavigationMode.Stack)
788      .borderWidth(1)
789    }
790  }
791}
792
793@Component
794struct pageOneStack {
795  @Consume('pageInfo') pageInfo: NavPathStack;
796  @LocalStorageLink('PropA') PropA: string = 'Hello World';
797
798  build() {
799    NavDestination() {
800      Column() {
801        NavigationContentMsgStack()
802        // 显示绑定的LocalStorage中PropA的值'PropA'
803        Text(`${this.PropA}`)
804        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
805          .width('80%')
806          .height(40)
807          .margin(20)
808          .onClick(() => {
809            this.pageInfo.pushPathByName('pageTwo', null);
810          })
811      }.width('100%').height('100%')
812    }.title('pageOne')
813    .onBackPressed(() => {
814      this.pageInfo.pop();
815      return true;
816    })
817  }
818}
819
820@Component
821struct pageTwoStack {
822  @Consume('pageInfo') pageInfo: NavPathStack;
823  @LocalStorageLink('PropB') PropB: string = 'Hello World';
824
825  build() {
826    NavDestination() {
827      Column() {
828        NavigationContentMsgStack()
829        // 如果绑定的LocalStorage中没有PropB,显示本地初始化的值 'Hello World'
830        Text(`${this.PropB}`)
831        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
832          .width('80%')
833          .height(40)
834          .margin(20)
835          .onClick(() => {
836            this.pageInfo.pushPathByName('pageThree', null);
837          })
838
839      }.width('100%').height('100%')
840    }.title('pageTwo')
841    .onBackPressed(() => {
842      this.pageInfo.pop();
843      return true;
844    })
845  }
846}
847
848@Component
849struct pageThreeStack {
850  @Consume('pageInfo') pageInfo: NavPathStack;
851  @LocalStorageLink('PropC') PropC: string = 'pageThreeStack';
852
853  build() {
854    NavDestination() {
855      Column() {
856        NavigationContentMsgStack()
857
858        // 如果绑定的LocalStorage中没有PropC,显示本地初始化的值 'pageThreeStack'
859        Text(`${this.PropC}`)
860        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
861          .width('80%')
862          .height(40)
863          .margin(20)
864          .onClick(() => {
865            this.pageInfo.pushPathByName('pageOne', null);
866          })
867
868      }.width('100%').height('100%')
869    }.title('pageThree')
870    .onBackPressed(() => {
871      this.pageInfo.pop();
872      return true;
873    })
874  }
875}
876
877@Component
878struct NavigationContentMsgStack {
879  @LocalStorageLink('PropA') PropA: string = 'Hello';
880
881  build() {
882    Column() {
883      Text(`${this.PropA}`)
884        .fontSize(30)
885        .fontWeight(FontWeight.Bold)
886    }
887  }
888}
889```
890
891
892### LocalStorage支持联合类型
893
894在下面的示例中,变量A的类型为number | null,变量B的类型为number | undefined。Text组件初始化分别显示为null和undefined,点击切换为数字,再次点击切换回null和undefined。
895
896```ts
897@Component
898struct LocalStorLink {
899  @LocalStorageLink("LinkA") LinkA: number | null = null;
900  @LocalStorageLink("LinkB") LinkB: number | undefined = undefined;
901
902  build() {
903    Column() {
904      Text("@LocalStorageLink接口初始化,@LocalStorageLink取值")
905      Text(this.LinkA + "").fontSize(20).onClick(() => {
906        this.LinkA ? this.LinkA = null : this.LinkA = 1;
907      })
908      Text(this.LinkB + "").fontSize(20).onClick(() => {
909        this.LinkB ? this.LinkB = undefined : this.LinkB = 1;
910      })
911    }
912    .borderWidth(3).borderColor(Color.Green)
913
914  }
915}
916
917@Component
918struct LocalStorProp {
919  @LocalStorageProp("PropA") PropA: number | null = null;
920  @LocalStorageProp("PropB") PropB: number | undefined = undefined;
921
922  build() {
923    Column() {
924      Text("@LocalStorageProp接口初始化,@LocalStorageProp取值")
925      Text(this.PropA + "").fontSize(20).onClick(() => {
926        this.PropA ? this.PropA = null : this.PropA = 1;
927      })
928      Text(this.PropB + "").fontSize(20).onClick(() => {
929        this.PropB ? this.PropB = undefined : this.PropB = 1;
930      })
931    }
932    .borderWidth(3).borderColor(Color.Yellow)
933
934  }
935}
936
937let storage: LocalStorage = new LocalStorage();
938
939@Entry(storage)
940@Component
941struct Index {
942  build() {
943    Row() {
944      Column() {
945        LocalStorLink()
946        LocalStorProp()
947      }
948      .width('100%')
949    }
950    .height('100%')
951  }
952}
953```
954
955
956### 装饰Date类型变量
957
958> **说明:**
959>
960> 从API version 12开始,LocalStorage支持Date类型。
961
962在下面的示例中,@LocalStorageLink装饰的selectedDate类型为Date,点击Button改变selectedDate的值,视图会随之刷新。
963
964```ts
965@Entry
966@Component
967struct LocalDateSample {
968  @LocalStorageLink("date") selectedDate: Date = new Date('2021-08-08');
969
970  build() {
971    Column() {
972      Button('set selectedDate to 2023-07-08')
973        .margin(10)
974        .onClick(() => {
975          this.selectedDate = new Date('2023-07-08');
976        })
977      Button('increase the year by 1')
978        .margin(10)
979        .onClick(() => {
980          this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
981        })
982      Button('increase the month by 1')
983        .margin(10)
984        .onClick(() => {
985          this.selectedDate.setMonth(this.selectedDate.getMonth() + 1);
986        })
987      Button('increase the day by 1')
988        .margin(10)
989        .onClick(() => {
990          this.selectedDate.setDate(this.selectedDate.getDate() + 1);
991        })
992      DatePicker({
993        start: new Date('1970-1-1'),
994        end: new Date('2100-1-1'),
995        selected: $$this.selectedDate
996      })
997    }.width('100%')
998  }
999}
1000```
1001
1002
1003### 装饰Map类型变量
1004
1005> **说明:**
1006>
1007> 从API version 12开始,LocalStorage支持Map类型。
1008
1009在下面的示例中,@LocalStorageLink装饰的message类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。
1010
1011```ts
1012@Entry
1013@Component
1014struct LocalMapSample {
1015  @LocalStorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]);
1016
1017  build() {
1018    Row() {
1019      Column() {
1020        ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
1021          Text(`${item[0]}`).fontSize(30)
1022          Text(`${item[1]}`).fontSize(30)
1023          Divider()
1024        })
1025        Button('init map').onClick(() => {
1026          this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]);
1027        })
1028        Button('set new one').onClick(() => {
1029          this.message.set(4, "d");
1030        })
1031        Button('clear').onClick(() => {
1032          this.message.clear();
1033        })
1034        Button('replace the existing one').onClick(() => {
1035          this.message.set(0, "aa");
1036        })
1037        Button('delete the existing one').onClick(() => {
1038          this.message.delete(0);
1039        })
1040      }
1041      .width('100%')
1042    }
1043    .height('100%')
1044  }
1045}
1046```
1047
1048
1049### 装饰Set类型变量
1050
1051> **说明:**
1052>
1053> 从API version 12开始,LocalStorage支持Set类型。
1054
1055在下面的示例中,@LocalStorageLink装饰的memberSet类型为Set\<number\>,点击Button改变memberSet的值,视图会随之刷新。
1056
1057```ts
1058@Entry
1059@Component
1060struct LocalSetSample {
1061  @LocalStorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]);
1062
1063  build() {
1064    Row() {
1065      Column() {
1066        ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => {
1067          Text(`${item[0]}`)
1068            .fontSize(30)
1069          Divider()
1070        })
1071        Button('init set')
1072          .onClick(() => {
1073            this.memberSet = new Set([0, 1, 2, 3, 4]);
1074          })
1075        Button('set new one')
1076          .onClick(() => {
1077            this.memberSet.add(5);
1078          })
1079        Button('clear')
1080          .onClick(() => {
1081            this.memberSet.clear();
1082          })
1083        Button('delete the first one')
1084          .onClick(() => {
1085            this.memberSet.delete(0);
1086          })
1087      }
1088      .width('100%')
1089    }
1090    .height('100%')
1091  }
1092}
1093```
1094
1095### 自定义组件外改变状态变量
1096
1097```ts
1098let storage = new LocalStorage();
1099storage.setOrCreate('count', 47);
1100
1101class Model {
1102  storage: LocalStorage = storage;
1103
1104  call(propName: string, value: number) {
1105    this.storage.setOrCreate<number>(propName, value);
1106  }
1107}
1108
1109let model: Model = new Model();
1110
1111@Entry({ storage: storage })
1112@Component
1113struct Test {
1114  @LocalStorageLink('count') count: number = 0;
1115
1116  build() {
1117    Column() {
1118      Text(`count值: ${this.count}`)
1119      Button('change')
1120        .onClick(() => {
1121          model.call('count', this.count + 1);
1122        })
1123    }
1124  }
1125}
1126```
1127
1128<!--no_check-->
1129