• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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实例可以在页面内共享,也可以通过getSharedLocalStorage接口,实现跨页面、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类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现-1)。<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
184    let storage = new LocalStorage();
185    storage.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. \@LocalStorageProp与\@LocalStorageLink不支持装饰Function类型的变量,框架会抛出运行时错误。
197
1983. LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。
199
2004. LocalStorage是页面级存储,[getSharedLocalStorage](../../reference/apis-arkui/js-apis-arkui-UIContext.md#getsharedlocalstorage12)接口仅能获取当前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页面通过getSharedLocalStorage获取当前stage共享的LocalStorage实例。
480>
481> this.getUIContext().getSharedLocalStorage()只在模拟器或者实机上才有效,在Previewer预览器中使用不生效。
482
483在下面的用例中,Index页面中的propA通过使用共享的LocalStorage实例。点击Button跳转到Page页面,点击Change propA改变propA的值,back回Index页面后,页面中propA的值也同步修改。
484```ts
485// index.ets
486
487// 预览器上不支持获取页面共享的LocalStorage实例。
488@Entry({ useSharedStorage: true })
489@Component
490struct Index {
491  // 可以使用@LocalStorageLink/Prop与LocalStorage实例中的变量建立联系
492  @LocalStorageLink('PropA') propA: number = 1;
493  pageStack: NavPathStack = new NavPathStack();
494
495  build() {
496    Navigation(this.pageStack) {
497      Row(){
498        Column() {
499          Text(`${this.propA}`)
500            .fontSize(50)
501            .fontWeight(FontWeight.Bold)
502          Button("To Page")
503            .onClick(() => {
504              this.pageStack.pushPathByName('Page', null);
505            })
506        }
507        .width('100%')
508      }
509      .height('100%')
510    }
511  }
512}
513```
514
515```ts
516// Page.ets
517
518@Builder
519export function PageBuilder() {
520  Page()
521}
522
523// Page组件获得了父亲Index组件的LocalStorage实例
524@Component
525struct Page {
526  @LocalStorageLink('PropA') propA: number = 2;
527  pathStack: NavPathStack = new NavPathStack();
528
529  build() {
530    NavDestination() {
531      Row(){
532        Column() {
533          Text(`${this.propA}`)
534            .fontSize(50)
535            .fontWeight(FontWeight.Bold)
536
537          Button("Change propA")
538            .onClick(() => {
539              this.propA = 100;
540            })
541
542          Button("Back Index")
543            .onClick(() => {
544              this.pathStack.pop();
545            })
546        }
547        .width('100%')
548      }
549    }
550    .onReady((context: NavDestinationContext) => {
551      this.pathStack = context.pathStack;
552    })
553  }
554}
555```
556使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。
557```json
558{
559  "routerMap": [
560    {
561      "name": "Page",
562      "pageSourceFile": "src/main/ets/pages/Page.ets",
563      "buildFunction": "PageBuilder",
564      "data": {
565        "description" : "LocalStorage example"
566      }
567    }
568  ]
569}
570```
571
572> **说明:**
573>
574> 对于开发者更建议使用这个方式来构建LocalStorage的实例,并且在创建LocalStorage实例的时候就写入默认值,因为默认值可以作为运行异常的备份,也可以用作页面的单元测试。
575
576
577### 自定义组件接收LocalStorage实例
578
579除了根节点可通过@Entry来接收LocalStorage实例,自定义组件(子节点)也可以通过构造参数来传递LocalStorage实例。
580
581本示例以\@LocalStorageLink为例,展示了:
582
583- 父组件中的Text,显示LocalStorage实例localStorage1中PropA的值为“PropA”。
584
585- Child组件中,Text绑定的PropB,显示LocalStorage实例localStorage2中PropB的值为“PropB”。
586
587> **说明:**
588>
589> 从API version 12开始,自定义组件支持接收LocalStorage实例。
590> 当自定义组件作为子节点,定义了成员属性时,LocalStorage实例必须要放在第二个参数位置传递,否则会报类型不匹配的编译问题。
591> 当在自定义组件中定义了属性时,暂时不支持只有一个LocalStorage实例作为入参。如果没定义属性,可以只传入一个LocalStorage实例作为入参。
592> 如果定义的属性不需要从父组件初始化变量,则第一个参数需要传{}。
593> 作为构造参数传给子组件的LocalStorage实例在初始化时就会被决定,可以通过@LocalStorageLink或者LocalStorage的API修改LocalStorage实例中保存的属性值,但LocalStorage实例自身不能被动态修改。
594
595```ts
596let localStorage1: LocalStorage = new LocalStorage();
597localStorage1.setOrCreate('PropA', 'PropA');
598
599let localStorage2: LocalStorage = new LocalStorage();
600localStorage2.setOrCreate('PropB', 'PropB');
601
602@Entry(localStorage1)
603@Component
604struct Index {
605  // 'PropA',和localStorage1中'PropA'的双向同步
606  @LocalStorageLink('PropA') PropA: string = 'Hello World';
607  @State count: number = 0;
608
609  build() {
610    Row() {
611      Column() {
612        Text(this.PropA)
613          .fontSize(50)
614          .fontWeight(FontWeight.Bold)
615        // 使用LocalStorage 实例localStorage2
616        Child({ count: this.count }, localStorage2)
617      }
618      .width('100%')
619    }
620    .height('100%')
621  }
622}
623
624
625@Component
626struct Child {
627  @Link count: number;
628  //  'Hello World',和localStorage2中'PropB'的双向同步,如果localStorage2中没有'PropB',则使用默认值'Hello World'
629  @LocalStorageLink('PropB') PropB: string = 'Hello World';
630
631  build() {
632    Text(this.PropB)
633      .fontSize(50)
634      .fontWeight(FontWeight.Bold)
635  }
636}
637```
638
6391. 当自定义组件没有定义属性时,可以只传入一个LocalStorage实例作为入参。
640
641    ```ts
642    let localStorage1: LocalStorage = new LocalStorage();
643    localStorage1.setOrCreate('PropA', 'PropA');
644
645    let localStorage2: LocalStorage = new LocalStorage();
646    localStorage2.setOrCreate('PropB', 'PropB');
647
648    @Entry(localStorage1)
649    @Component
650    struct Index {
651      // 'PropA',和localStorage1中'PropA'的双向同步
652      @LocalStorageLink('PropA') PropA: string = 'Hello World';
653      @State count: number = 0;
654
655      build() {
656        Row() {
657          Column() {
658            Text(this.PropA)
659              .fontSize(50)
660              .fontWeight(FontWeight.Bold)
661            // 使用LocalStorage 实例localStorage2
662            Child(localStorage2)
663          }
664          .width('100%')
665        }
666        .height('100%')
667      }
668    }
669
670
671    @Component
672    struct Child {
673      build() {
674        Text("hello")
675          .fontSize(50)
676          .fontWeight(FontWeight.Bold)
677      }
678    }
679    ```
680
6812. 当定义的属性不需要从父组件初始化变量时,第一个参数需要传{}。
682
683    ```ts
684    let localStorage1: LocalStorage = new LocalStorage();
685    localStorage1.setOrCreate('PropA', 'PropA');
686
687    let localStorage2: LocalStorage = new LocalStorage();
688    localStorage2.setOrCreate('PropB', 'PropB');
689
690    @Entry(localStorage1)
691    @Component
692    struct Index {
693      // 'PropA',和localStorage1中'PropA'的双向同步
694      @LocalStorageLink('PropA') PropA: string = 'Hello World';
695      @State count: number = 0;
696
697      build() {
698        Row() {
699          Column() {
700            Text(this.PropA)
701              .fontSize(50)
702              .fontWeight(FontWeight.Bold)
703            // 使用LocalStorage 实例localStorage2
704            Child({}, localStorage2)
705          }
706          .width('100%')
707        }
708        .height('100%')
709      }
710    }
711
712
713    @Component
714    struct Child {
715      @State count: number = 5;
716      // 'Hello World',和localStorage2中'PropB'的双向同步,如果localStorage2中没有'PropB',则使用默认值'Hello World'
717      @LocalStorageLink('PropB') PropB: string = 'Hello World';
718
719      build() {
720        Text(this.PropB)
721          .fontSize(50)
722          .fontWeight(FontWeight.Bold)
723      }
724    }
725    ```
726
727
728### Navigation组件和LocalStorage联合使用
729
730可以通过传递不同的LocalStorage实例给自定义组件,从而实现在navigation跳转到不同的页面时,绑定不同的LocalStorage实例,显示对应绑定的值。
731
732本示例以\@LocalStorageLink为例,展示了:
733
734- 点击父组件中的Button "Next Page",创建并跳转到name为"pageOne"的子页面,Text显示信息为LocalStorage实例localStorageA中绑定的PropA的值,为"PropA"。
735
736- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageTwo"的子页面,Text显示信息为LocalStorage实例localStorageB中绑定的PropB的值,为"PropB"。
737
738- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageTree"的子页面,Text显示信息为LocalStorage实例localStorageC中绑定的PropC的值,为"PropC"。
739
740- 继续点击页面上的Button "Next Page",创建并跳转到name为"pageOne"的子页面,Text显示信息为LocalStorage实例localStorageA中绑定的PropA的值,为"PropA"。
741
742- NavigationContentMsgStack自定义组件中的Text组件,共享对应自定义组件树上LocalStorage实例绑定的PropA的值。
743
744
745```ts
746let localStorageA: LocalStorage = new LocalStorage();
747localStorageA.setOrCreate('PropA', 'PropA');
748
749let localStorageB: LocalStorage = new LocalStorage();
750localStorageB.setOrCreate('PropB', 'PropB');
751
752let localStorageC: LocalStorage = new LocalStorage();
753localStorageC.setOrCreate('PropC', 'PropC');
754
755@Entry
756@Component
757struct MyNavigationTestStack {
758  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
759
760  @Builder
761  PageMap(name: string) {
762    if (name === 'pageOne') {
763      // 传递不同的LocalStorage实例
764      pageOneStack({}, localStorageA)
765    } else if (name === 'pageTwo') {
766      pageTwoStack({}, localStorageB)
767    } else if (name === 'pageThree') {
768      pageThreeStack({}, localStorageC)
769    }
770  }
771
772  build() {
773    Column({ space: 5 }) {
774      Navigation(this.pageInfo) {
775        Column() {
776          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
777            .width('80%')
778            .height(40)
779            .margin(20)
780            .onClick(() => {
781              this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈
782            })
783        }
784      }.title('NavIndex')
785      .navDestination(this.PageMap)
786      .mode(NavigationMode.Stack)
787      .borderWidth(1)
788    }
789  }
790}
791
792@Component
793struct pageOneStack {
794  @Consume('pageInfo') pageInfo: NavPathStack;
795  @LocalStorageLink('PropA') PropA: string = 'Hello World';
796
797  build() {
798    NavDestination() {
799      Column() {
800        NavigationContentMsgStack()
801        // 显示绑定的LocalStorage中PropA的值'PropA'
802        Text(`${this.PropA}`)
803        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
804          .width('80%')
805          .height(40)
806          .margin(20)
807          .onClick(() => {
808            this.pageInfo.pushPathByName('pageTwo', null);
809          })
810      }.width('100%').height('100%')
811    }.title('pageOne')
812    .onBackPressed(() => {
813      this.pageInfo.pop();
814      return true;
815    })
816  }
817}
818
819@Component
820struct pageTwoStack {
821  @Consume('pageInfo') pageInfo: NavPathStack;
822  @LocalStorageLink('PropB') PropB: string = 'Hello World';
823
824  build() {
825    NavDestination() {
826      Column() {
827        NavigationContentMsgStack()
828        // 如果绑定的LocalStorage中没有PropB,显示本地初始化的值 'Hello World'
829        Text(`${this.PropB}`)
830        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
831          .width('80%')
832          .height(40)
833          .margin(20)
834          .onClick(() => {
835            this.pageInfo.pushPathByName('pageThree', null);
836          })
837
838      }.width('100%').height('100%')
839    }.title('pageTwo')
840    .onBackPressed(() => {
841      this.pageInfo.pop();
842      return true;
843    })
844  }
845}
846
847@Component
848struct pageThreeStack {
849  @Consume('pageInfo') pageInfo: NavPathStack;
850  @LocalStorageLink('PropC') PropC: string = 'pageThreeStack';
851
852  build() {
853    NavDestination() {
854      Column() {
855        NavigationContentMsgStack()
856
857        // 如果绑定的LocalStorage中没有PropC,显示本地初始化的值 'pageThreeStack'
858        Text(`${this.PropC}`)
859        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
860          .width('80%')
861          .height(40)
862          .margin(20)
863          .onClick(() => {
864            this.pageInfo.pushPathByName('pageOne', null);
865          })
866
867      }.width('100%').height('100%')
868    }.title('pageThree')
869    .onBackPressed(() => {
870      this.pageInfo.pop();
871      return true;
872    })
873  }
874}
875
876@Component
877struct NavigationContentMsgStack {
878  @LocalStorageLink('PropA') PropA: string = 'Hello';
879
880  build() {
881    Column() {
882      Text(`${this.PropA}`)
883        .fontSize(30)
884        .fontWeight(FontWeight.Bold)
885    }
886  }
887}
888```
889
890
891### LocalStorage支持联合类型
892
893在下面的示例中,变量A的类型为number | null,变量B的类型为number | undefined。Text组件初始化分别显示为null和undefined,点击切换为数字,再次点击切换回null和undefined。
894
895```ts
896@Component
897struct LocalStorLink {
898  @LocalStorageLink("LinkA") LinkA: number | null = null;
899  @LocalStorageLink("LinkB") LinkB: number | undefined = undefined;
900
901  build() {
902    Column() {
903      Text("@LocalStorageLink接口初始化,@LocalStorageLink取值")
904      Text(this.LinkA + "").fontSize(20).onClick(() => {
905        this.LinkA ? this.LinkA = null : this.LinkA = 1;
906      })
907      Text(this.LinkB + "").fontSize(20).onClick(() => {
908        this.LinkB ? this.LinkB = undefined : this.LinkB = 1;
909      })
910    }
911    .borderWidth(3).borderColor(Color.Green)
912
913  }
914}
915
916@Component
917struct LocalStorProp {
918  @LocalStorageProp("PropA") PropA: number | null = null;
919  @LocalStorageProp("PropB") PropB: number | undefined = undefined;
920
921  build() {
922    Column() {
923      Text("@LocalStorageProp接口初始化,@LocalStorageProp取值")
924      Text(this.PropA + "").fontSize(20).onClick(() => {
925        this.PropA ? this.PropA = null : this.PropA = 1;
926      })
927      Text(this.PropB + "").fontSize(20).onClick(() => {
928        this.PropB ? this.PropB = undefined : this.PropB = 1;
929      })
930    }
931    .borderWidth(3).borderColor(Color.Yellow)
932
933  }
934}
935
936let storage: LocalStorage = new LocalStorage();
937
938@Entry(storage)
939@Component
940struct Index {
941  build() {
942    Row() {
943      Column() {
944        LocalStorLink()
945        LocalStorProp()
946      }
947      .width('100%')
948    }
949    .height('100%')
950  }
951}
952```
953
954
955### 装饰Date类型变量
956
957> **说明:**
958>
959> 从API version 12开始,LocalStorage支持Date类型。
960
961在下面的示例中,@LocalStorageLink装饰的selectedDate类型为Date,点击Button改变selectedDate的值,视图会随之刷新。
962
963```ts
964@Entry
965@Component
966struct LocalDateSample {
967  @LocalStorageLink("date") selectedDate: Date = new Date('2021-08-08');
968
969  build() {
970    Column() {
971      Button('set selectedDate to 2023-07-08')
972        .margin(10)
973        .onClick(() => {
974          this.selectedDate = new Date('2023-07-08');
975        })
976      Button('increase the year by 1')
977        .margin(10)
978        .onClick(() => {
979          this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
980        })
981      Button('increase the month by 1')
982        .margin(10)
983        .onClick(() => {
984          this.selectedDate.setMonth(this.selectedDate.getMonth() + 1);
985        })
986      Button('increase the day by 1')
987        .margin(10)
988        .onClick(() => {
989          this.selectedDate.setDate(this.selectedDate.getDate() + 1);
990        })
991      DatePicker({
992        start: new Date('1970-1-1'),
993        end: new Date('2100-1-1'),
994        selected: $$this.selectedDate
995      })
996    }.width('100%')
997  }
998}
999```
1000
1001
1002### 装饰Map类型变量
1003
1004> **说明:**
1005>
1006> 从API version 12开始,LocalStorage支持Map类型。
1007
1008在下面的示例中,@LocalStorageLink装饰的message类型为Map\<number, string\>,点击Button改变message的值,视图会随之刷新。
1009
1010```ts
1011@Entry
1012@Component
1013struct LocalMapSample {
1014  @LocalStorageLink("map") message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]);
1015
1016  build() {
1017    Row() {
1018      Column() {
1019        ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
1020          Text(`${item[0]}`).fontSize(30)
1021          Text(`${item[1]}`).fontSize(30)
1022          Divider()
1023        })
1024        Button('init map').onClick(() => {
1025          this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]);
1026        })
1027        Button('set new one').onClick(() => {
1028          this.message.set(4, "d");
1029        })
1030        Button('clear').onClick(() => {
1031          this.message.clear();
1032        })
1033        Button('replace the existing one').onClick(() => {
1034          this.message.set(0, "aa");
1035        })
1036        Button('delete the existing one').onClick(() => {
1037          this.message.delete(0);
1038        })
1039      }
1040      .width('100%')
1041    }
1042    .height('100%')
1043  }
1044}
1045```
1046
1047
1048### 装饰Set类型变量
1049
1050> **说明:**
1051>
1052> 从API version 12开始,LocalStorage支持Set类型。
1053
1054在下面的示例中,@LocalStorageLink装饰的memberSet类型为Set\<number\>,点击Button改变memberSet的值,视图会随之刷新。
1055
1056```ts
1057@Entry
1058@Component
1059struct LocalSetSample {
1060  @LocalStorageLink("set") memberSet: Set<number> = new Set([0, 1, 2, 3, 4]);
1061
1062  build() {
1063    Row() {
1064      Column() {
1065        ForEach(Array.from(this.memberSet.entries()), (item: [number, string]) => {
1066          Text(`${item[0]}`)
1067            .fontSize(30)
1068          Divider()
1069        })
1070        Button('init set')
1071          .onClick(() => {
1072            this.memberSet = new Set([0, 1, 2, 3, 4]);
1073          })
1074        Button('set new one')
1075          .onClick(() => {
1076            this.memberSet.add(5);
1077          })
1078        Button('clear')
1079          .onClick(() => {
1080            this.memberSet.clear();
1081          })
1082        Button('delete the first one')
1083          .onClick(() => {
1084            this.memberSet.delete(0);
1085          })
1086      }
1087      .width('100%')
1088    }
1089    .height('100%')
1090  }
1091}
1092```
1093
1094### 自定义组件外改变状态变量
1095
1096```ts
1097let storage = new LocalStorage();
1098storage.setOrCreate('count', 47);
1099
1100class Model {
1101  storage: LocalStorage = storage;
1102
1103  call(propName: string, value: number) {
1104    this.storage.setOrCreate<number>(propName, value);
1105  }
1106}
1107
1108let model: Model = new Model();
1109
1110@Entry({ storage: storage })
1111@Component
1112struct Test {
1113  @LocalStorageLink('count') count: number = 0;
1114
1115  build() {
1116    Column() {
1117      Text(`count值: ${this.count}`)
1118      Button('change')
1119        .onClick(() => {
1120          model.call('count', this.count + 1);
1121        })
1122    }
1123  }
1124}
1125```
1126
1127<!--no_check-->
1128