# LocalStorage:页面级UI状态存储
LocalStorage是页面级的UI状态存储,通过\@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage支持UIAbility实例内多个页面间状态共享。
本文仅介绍LocalStorage使用场景和相关的装饰器:\@LocalStorageProp和\@LocalStorageLink。
在阅读本文档前,建议开发者对状态管理框架有基本的了解。建议提前阅读:[状态管理概述](./arkts-state-management-overview.md)。
LocalStorage还提供了API接口,可以让开发者通过接口在自定义组件外手动触发Storage对应key的增删改查,建议配合[LocalStorage API文档](../reference/apis-arkui/arkui-ts/ts-state-management.md#localstorage9)阅读。
> **说明:**
>
> LocalStorage从API version 9开始支持。
## 概述
LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内的“数据库”。
- 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过getShared接口,实现跨页面、UIAbility实例内共享。
- 组件树的根节点,即被\@Entry装饰的\@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限。
- \@Component装饰的组件既可以自动继承来自父组件的LocalStorage实例,也可以传入指定的LocalStorage的实例,详见:[自定义组件接收LocalStorage实例](#自定义组件接收localstorage实例)。
- LocalStorage中的所有属性都是可变的。
应用程序决定LocalStorage对象的生命周期。当应用释放最后一个指向LocalStorage的引用时,比如销毁最后一个自定义组件,LocalStorage将被JS Engine垃圾回收。
LocalStorage根据与\@Component装饰的组件的同步类型不同,提供了两个装饰器:
- [@LocalStorageProp](#localstorageprop):\@LocalStorageProp装饰的变量与LocalStorage中给定属性建立单向同步关系。
- [@LocalStorageLink](#localstoragelink):\@LocalStorageLink装饰的变量与LocalStorage中给定属性建立双向同步关系。
## \@LocalStorageProp
在上文中已经提到,如果要建立LocalStorage和自定义组件的联系,需要使用\@LocalStorageProp和\@LocalStorageLink装饰器。使用\@LocalStorageProp(key)/\@LocalStorageLink(key)装饰组件内的变量,key标识了LocalStorage的属性。
当自定义组件初始化的时候,\@LocalStorageProp(key)/\@LocalStorageLink(key)装饰的变量会通过给定的key,绑定LocalStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证LocalStorage一定存在给定的key(这取决于应用逻辑是否在组件初始化之前在LocalStorage实例中存入对应的属性)。
> **说明:**
>
> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。
>
> 从API version 11开始,该装饰器支持在原子化服务中使用。
\@LocalStorageProp(key)是和LocalStorage中key对应的属性建立单向数据同步,ArkUI框架支持修改@LocalStorageProp(key)在本地的值,但是对本地值的修改不会同步回LocalStorage中。相反,如果LocalStorage中key对应的属性值发生改变,例如通过set接口对LocalStorage中的值进行修改,改变会同步给\@LocalStorageProp(key),并覆盖掉本地的值。
### 装饰器使用规则说明
| \@LocalStorageProp变量装饰器 | 说明 |
| ----------------------- | ---------------------------------------- |
| 装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 |
| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。
API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。
类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
不支持any,API12及以上支持undefined和null类型。
API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[LocalStorage支持联合类型](#localstorage支持联合类型)。
**注意**
当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@LocalStorageProp("AA") a: number \| null = null`是推荐的,不推荐`@LocalStorageProp("AA") a: number = null`。 |
| 同步类型 | 单向同步:从LocalStorage的对应属性到组件的状态变量。组件本地的修改是允许的,但是LocalStorage中给定的属性一旦发生变化,将覆盖本地的修改。 |
| 被装饰变量的初始值 | 必须指定,如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。 |
### 变量的传递/访问规则说明
| 传递/访问 | 说明 |
| ---------- | ---------------------------------------- |
| 从父节点初始化和更新 | 禁止,\@LocalStorageProp不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化。 |
| 初始化子节点 | 支持,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
| 是否支持组件外访问 | 否。 |
**图1** \@LocalStorageProp初始化规则图示

### 观察变化和行为表现
**观察变化**
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用localstorage](#从ui内部使用localstorage))。
- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。
- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
**框架行为**
- 被\@LocalStorageProp装饰的变量的值的变化不会同步回LocalStorage里。
- \@LocalStorageProp装饰的变量变化会使当前自定义组件中关联的组件刷新。
- LocalStorage(key)中值的变化会引发所有被\@LocalStorageProp对应key装饰的变量的变化,会覆盖\@LocalStorageProp本地的改变。

## \@LocalStorageLink
> **说明:**
>
> 从API version 11开始,该装饰器支持在原子化服务中使用。
如果我们需要将自定义组件的状态变量的更新同步回LocalStorage,就需要用到\@LocalStorageLink。
\@LocalStorageLink(key)是和LocalStorage中key对应的属性建立双向数据同步:
1. 本地修改发生,该修改会被写回LocalStorage中;
2. LocalStorage中的修改发生后,该修改会被同步到所有绑定LocalStorage对应key的属性上,包括单向(\@LocalStorageProp和通过prop创建的单向绑定变量)、双向(\@LocalStorageLink和通过link创建的双向绑定变量)变量。
### 装饰器使用规则说明
| \@LocalStorageLink变量装饰器 | 说明 |
| ----------------------- | ---------------------------------------- |
| 装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 |
| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。
API12及以上支持Map、Set、Date类型。嵌套类型的场景请参考[观察变化和行为表现](#观察变化和行为表现)。
类型必须被指定,建议和LocalStorage中对应属性类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
不支持any,API12及以上支持undefined和null类型。
API12及以上支持上述支持类型的联合类型,比如string \| number, string \| undefined 或者 ClassA \| null,示例见[LocalStorage支持联合类型](#localstorage支持联合类型)。
**注意**
当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:`@LocalStorageLink("AA") a: number \| null = null`是推荐的,不推荐`@LocalStorageLink("AA") a: number = null`。 |
| 同步类型 | 双向同步:从LocalStorage的对应属性到自定义组件,从自定义组件到LocalStorage对应属性。 |
| 被装饰变量的初始值 | 必须指定,如果LocalStorage实例中不存在属性,则用该初始值初始化该属性,并存入LocalStorage中。 |
### 变量的传递/访问规则说明
| 传递/访问 | 说明 |
| ---------- | ---------------------------------------- |
| 从父节点初始化和更新 | 禁止,\@LocalStorageLink不支持从父节点初始化,只能从LocalStorage中key对应的属性初始化,如果没有对应key的话,将使用本地默认值初始化。 |
| 初始化子节点 | 支持,可用于初始化\@State、\@Link、\@Prop、\@Provide。 |
| 是否支持组件外访问 | 否。 |
**图2** \@LocalStorageLink初始化规则图示

### 观察变化和行为表现
**观察变化**
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者Object时,可以观察到对象整体赋值和对象属性变化(详见[从ui内部使用localstorage](#从ui内部使用localstorage))。
- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。
- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。详见[装饰Date类型变量](#装饰date类型变量)。
- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口`set`, `clear`, `delete` 更新Map的值。详见[装饰Map类型变量](#装饰map类型变量)。
- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口`add`, `clear`, `delete` 更新Set的值。详见[装饰Set类型变量](#装饰set类型变量)。
**框架行为**
1. 当\@LocalStorageLink(key)装饰的数值改变被观察到时,修改将被同步回LocalStorage对应属性键值key的属性中。
2. LocalStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向\@LocalStorageLink和单向\@LocalStorageProp)都将同步修改。
3. 当\@LocalStorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回LocalStorage中,还会引起所属的自定义组件的重新渲染。

## 限制条件
1. \@LocalStorageProp/\@LocalStorageLink的参数必须为string类型,否则编译期会报错。
```ts
let storage = new LocalStorage();
storage.setOrCreate('PropA', 48);
// 错误写法,编译报错
@LocalStorageProp() localStorageProp: number = 1;
@LocalStorageLink() localStorageLink: number = 2;
// 正确写法
@LocalStorageProp('PropA') localStorageProp: number = 1;
@LocalStorageLink('PropA') localStorageLink: number = 2;
```
2. \@StorageProp与\@StorageLink不支持装饰Function类型的变量,框架会抛出运行时错误。
3. LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。
4. 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共享到一个或多个视图)。
## 使用场景
### 应用逻辑使用LocalStorage
```ts
let para: Record = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化
let propA: number | undefined = storage.get('PropA'); // propA == 47
let link1: SubscribedAbstractProperty = storage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty = storage.link('PropA'); // link2.get() == 47
let prop: SubscribedAbstractProperty = storage.prop('PropA'); // prop.get() == 47
link1.set(48); // 双向同步: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // 单向同步: prop.get() == 1; 但 link1.get() == link2.get() == 48
link1.set(49); // 双向同步: link1.get() == link2.get() == prop.get() == 49
```
### 从UI内部使用LocalStorage
除了应用程序逻辑使用LocalStorage,还可以借助LocalStorage相关的两个装饰器\@LocalStorageProp和\@LocalStorageLink,在UI组件内部获取到LocalStorage实例中存储的状态变量。
本示例以\@LocalStorageLink为例,展示了:
- 使用构造函数创建LocalStorage实例storage;
- 使用\@Entry装饰器将storage添加到Parent顶层组件中;
- \@LocalStorageLink绑定LocalStorage对给定的属性,建立双向数据同步。
```ts
class Data {
code: number;
constructor(code: number) {
this.code = code;
}
}
// 创建新实例并使用给定对象初始化
let para: Record = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
storage.setOrCreate('PropB', new Data(50));
@Component
struct Child {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') childLinkNumber: number = 1;
// @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
@LocalStorageLink('PropB') childLinkObject: Data = new Data(0);
build() {
Column({ space: 15 }) {
Button(`Child from LocalStorage ${this.childLinkNumber}`) // 更改将同步至LocalStorage中的'PropA'以及Parent.parentLinkNumber
.onClick(() => {
this.childLinkNumber += 1;
})
Button(`Child from LocalStorage ${this.childLinkObject.code}`) // 更改将同步至LocalStorage中的'PropB'以及Parent.parentLinkObject.code
.onClick(() => {
this.childLinkObject.code += 1;
})
}
}
}
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct Parent {
// @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
@LocalStorageLink('PropA') parentLinkNumber: number = 1;
// @LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定
@LocalStorageLink('PropB') parentLinkObject: Data = new Data(0);
build() {
Column({ space: 15 }) {
Button(`Parent from LocalStorage ${this.parentLinkNumber}`) // 由于LocalStorage中PropA已经被初始化,因此this.parentLinkNumber的值为47
.onClick(() => {
this.parentLinkNumber += 1;
})
Button(`Parent from LocalStorage ${this.parentLinkObject.code}`) // 由于LocalStorage中PropB已经被初始化,因此this.parentLinkObject.code的值为50
.onClick(() => {
this.parentLinkObject.code += 1;
})
// @Component子组件自动获得对Parent LocalStorage实例的访问权限。
Child()
}
}
}
```
### \@LocalStorageProp和LocalStorage单向同步的简单场景
在下面的示例中,Parent 组件和Child组件分别在本地创建了与storage的'PropA'对应属性的单向同步的数据,我们可以看到:
- Parent中对this.storageProp1的修改,只会在Parent中生效,并没有同步回storage;
- Child组件中,Text绑定的storageProp2 依旧显示47。
```ts
// 创建新实例并使用给定对象初始化
let para: Record = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct Parent {
// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
@LocalStorageProp('PropA') storageProp1: number = 1;
build() {
Column({ space: 15 }) {
// 点击后从47开始加1,只改变当前组件显示的storageProp1,不会同步到LocalStorage中
Button(`Parent from LocalStorage ${this.storageProp1}`)
.onClick(() => {
this.storageProp1 += 1;
})
Child()
}
}
}
@Component
struct Child {
// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
@LocalStorageProp('PropA') storageProp2: number = 2;
build() {
Column({ space: 15 }) {
// 当Parent改变时,当前storageProp2不会改变,显示47
Text(`Parent from LocalStorage ${this.storageProp2}`)
}
}
}
```
### \@LocalStorageLink和LocalStorage双向同步的简单场景
下面的示例展示了\@LocalStorageLink装饰的数据和LocalStorage双向同步的场景:
```ts
// 构造LocalStorage实例
let para: Record = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 调用link(api9以上)接口构造'PropA'的双向同步数据,linkToPropA 是全局变量
let linkToPropA: SubscribedAbstractProperty