鸿蒙应用状态管理新方案:AppStorageV2与PersistenceV2深度详解
在鸿蒙应用开发中,状态管理一直是构建复杂应用的核心挑战。从API 12开始,鸿蒙推出了全新的V2版本状态管理方案,其中AppStorageV2和PersistenceV2作为核心组件,为开发者提供了更强大、更灵活的状态管理能力。本文将深入剖析这两个组件的工作原理、使用方法和最佳实践。
文章目录
- 一、AppStorageV2:应用级全局UI状态存储
- 1.1 基本概念与特点
- 1.2 核心API详解
- connect方法:创建或获取数据
- remove方法:删除数据
- keys方法:获取所有存储的键
- 1.3 使用限制
- 1.4 与装饰器的配合使用
- @ObservedV2装饰类
- @Trace装饰属性
- 1.5 实际应用场景
- 跨页面数据共享
- 二、PersistenceV2:持久化存储UI状态
- 2.1 基本概念与特点
- 2.2 核心API详解
- connect方法:创建或获取数据
- save方法:手动持久化数据
- notifyOnError方法:响应序列化或反序列化失败的回调
- globalConnect方法(API 18+)
- 2.3 持久化规则与限制
- 自动持久化规则
- 支持的持久化类型
- 不支持的持久化类型
- 使用限制
- 2.4 实际应用场景
- 用户偏好设置持久化
- 登录状态持久化
- 三、AppStorageV2与PersistenceV2的配合使用
- 3.1 数据流关系
- 3.2 典型应用模式
- 3.3 多设备同步配置
- 四、从V1到V2的迁移指南
- 4.1 核心装饰器对应关系
- 4.2 迁移示例
- 五、最佳实践与注意事项
- 5.1 最佳实践
- 5.2 注意事项
- 六、总结
- 其他资源
一、AppStorageV2:应用级全局UI状态存储
1.1 基本概念与特点
AppStorageV2是在应用UI启动时会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorageV2将在应用运行过程保留其数据。数据通过唯一的键字符串值访问。
AppStorageV2具有以下核心特点:
- 单例模式:应用启动时自动创建,整个应用中唯一实例,确保数据一致性
- 全局可访问:在任何页面、任何UIAbility中都可访问
- 跨UIAbility共享:支持主线程内多个UIAbility实例间的数据共享
- 内存存储:数据在应用运行期间保留,应用关闭后数据丢失
- 数据隔离:与V1版本的AppStorage数据互不共享
- UI同步:可以和UI组件同步,且可以在应用业务逻辑中被访问
1.2 核心API详解
connect方法:创建或获取数据
static connect<T extends object>(type: TypeConstructorWithArgs<T>,keyOrDefaultCreator?: string | StorageDefaultCreator<T>,defaultCreator?: StorageDefaultCreator<T>
): T | undefined;
参数说明:
type:指定的类型,若未指定key,则使用type的name作为keykeyOrDefaultCreator:指定的key,或者是默认数据的构造器defaultCreator:默认数据的构造器
返回值: 成功返回指定类型的数据对象,失败返回undefined
说明:
- 若未指定key,使用第二个参数作为默认构造器;否则使用第三个参数作为默认构造器(第二个参数非法也使用第三个参数作为默认构造器)
- 确保数据已经存储在AppStorageV2中,可省略默认构造器,获取存储的数据;否则必须指定默认构造器,不指定将导致应用异常
- 同一个key,connect不同类型的数据会导致应用异常,应用需要确保类型匹配
- key建议使用有意义的值,可由字母、数字、下划线组成,长度不超过255,使用非法字符或空字符的行为是未定义的
- 关联@Observed对象时,由于该类型的name属性未定义,需要指定key或者自定义name属性
使用示例:
@ObservedV2
class UserInfo {@Trace name: string = "默认名称"age: number = 18
}// 方式1:不指定key,使用类型名作为key
let user1 = AppStorageV2.connect(UserInfo, () => new UserInfo())// 方式2:指定key和默认构造器
let user2 = AppStorageV2.connect(UserInfo, "user_key", () => new UserInfo())// 方式3:获取已存在的数据(确保数据已存在)
let existingUser = AppStorageV2.connect(UserInfo, "user_key")
remove方法:删除数据
static remove<T>(keyOrType: string | TypeConstructorWithArgs<T>): void;
参数说明:
keyOrType:需要删除的key;如果指定的是type类型,删除的key为type的name
返回值: 无
说明: 删除AppStorageV2中不存在的key会报警告
使用示例:
// 通过key删除
AppStorageV2.remove("user_key")// 通过类型删除
AppStorageV2.remove(UserInfo)
keys方法:获取所有存储的键
static keys(): Array<string>;
参数: 无
返回值: 所有AppStorageV2中的key
使用示例:
let allKeys = AppStorageV2.keys()
console.log("存储的键列表:" + allKeys.join(","))
1.3 使用限制
- 仅UI线程可用:需要配合UI使用(UI线程),不能在其他线程使用,如不支持@Sendable
- 不支持的特殊类型:不支持collections.Set、collections.Map等集合类型
- 非内置类型限制:不支持非buildin类型,如PixelMap、NativePointer、ArrayList等Native类型
- 类型一致性:同一个key的前后connect操作必须使用相同的类型
注意:AppStorageV2从API version 12开始支持
1.4 与装饰器的配合使用
@ObservedV2装饰类
用于标记可被观察的类,使其实例可以被多个UI组件共享和观察。
@ObservedV2
class Config {@Trace theme: string = "light";fontSize: number = 16;
}
@Trace装饰属性
用于标记需要触发UI刷新的属性。只有被@Trace装饰的属性发生变化时,才会自动触发UI组件的刷新。
@ObservedV2
class UserData {@Trace name: string = "默认用户"; // 修改时会触发UI刷新level: number = 1; // 修改时不会自动触发UI刷新
}
1.5 实际应用场景
跨页面数据共享
以下是两个页面通过AppStorageV2共享数据的完整示例:
1. 定义共享数据类型
// Sample.ets
@ObservedV2
export class Sample {@Trace p1: number = 0;p2: number = 10;
}
2. 第一个页面(Page1)
// Page1.ets
import { AppStorageV2 } from '@kit.ArkUI';
import { Sample } from '../Sample';@Entry
@ComponentV2
struct Page1 {@Local prop: Sample = AppStorageV2.connect(Sample, () => new Sample())!;pageStack: NavPathStack = new NavPathStack();build() {Navigation(this.pageStack) {Column({ space: 15 }) {Button('跳转到Page2').onClick(() => {this.pageStack.pushPathByName('Page2', null);})Text(`Page1中p1的值:${this.prop.p1}(点击+1)`).onClick(() => {this.prop.p1++; // p1有@Trace,修改后UI会刷新})Text(`Page1中p2的值:${this.prop.p2}(点击+1)`).onClick(() => {this.prop.p2++; // p2没有@Trace,修改后UI不会自动刷新})Text(`当前存储的key:${AppStorageV2.keys().join(',')}`)}}}
}
3. 第二个页面(Page2)
// Page2.ets
import { AppStorageV2 } from '@kit.ArkUI';
import { Sample } from '../Sample';@Builder
export function Page2Builder() {Page2()
}@ComponentV2
struct Page2 {@Local prop: Sample = AppStorageV2.connect(Sample, () => new Sample())!;pathStack: NavPathStack = new NavPathStack();build() {NavDestination() {Column({ space: 15 }) {Text(`Page2中p1的值:${this.prop.p1}(点击+1)`).onClick(() => {this.prop.p1++; // p1修改后UI会刷新})Text(`Page2中p2的值:${this.prop.p2}(点击+1)`).onClick(() => {this.prop.p2++; // p2修改后UI不自动刷新})}}.onReady((context: NavDestinationContext) => {this.pathStack = context.pathStack;})}
}
二、PersistenceV2:持久化存储UI状态
2.1 基本概念与特点
PersistenceV2是在应用UI启动时会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。数据通过唯一的键字符串值访问。不同于AppStorageV2,PersistenceV2还将最新数据储存在设备磁盘上(持久化)。这意味着,应用退出再次启动后,依然能保存选定的结果。
对于与PersistenceV2关联的@ObservedV2对象,该对象的@Trace属性的变化,会触发整个关联对象的自动持久化;非@Trace属性的变化则不会,如有必要,可调用PersistenceV2 API手动持久化。
PersistenceV2具有以下核心特点:
- 数据持久化:数据保存到设备磁盘,应用重启后数据依然保留
- 自动同步:被@Trace装饰的属性变化时,自动触发数据持久化
- 手动持久化:支持通过API手动触发非@Trace属性的数据持久化
- 错误回调:提供错误通知机制,处理序列化或反序列化失败情况
- UI同步:可以和UI组件同步,且可以在应用业务逻辑中被访问
- 跨UIAbility共享:支持应用的主线程内多个UIAbility实例间的状态共享
注意:PersistenceV2从API version 12开始支持
2.2 核心API详解
connect方法:创建或获取数据
static connect<T extends object>(type: TypeConstructorWithArgs<T>,keyOrDefaultCreator?: string | StorageDefaultCreator<T>,defaultCreator?: StorageDefaultCreator<T>,options?: { distributed?: boolean }
): T | undefined;
参数说明:
type:指定的类型,若未指定key,则使用type的name作为keykeyOrDefaultCreator:指定的key,或者是默认数据的构造器defaultCreator:默认数据的构造器options:可选,支持distributed: boolean配置,启用多设备数据同步
返回值: 成功返回指定类型的数据对象,失败返回undefined
说明:
- 若未指定key,使用第二个参数作为默认构造器;否则使用第三个参数作为默认构造器(第二个参数非法也使用第三个参数作为默认构造器)
- 确保数据已经存储在PersistenceV2中,可省略默认构造器,获取存储的数据;否则必须指定默认构造器,不指定将导致应用异常
- 同一个key,connect不同类型的数据会导致应用异常,应用需要确保类型匹配
- key建议使用有意义的值,可由字母、数字、下划线组成,长度不超过255,使用非法字符或空字符的行为是未定义的
- 关联@Observed对象时,由于该类型的name属性未定义,需要指定key或者自定义name属性
使用示例:
@ObservedV2
class UserSettings {@Trace theme: string = "light";fontSize: number = 16;
}// 基本使用
let settings = PersistenceV2.connect(UserSettings, "user_settings", () => new UserSettings())// 启用分布式(多设备同步)
let distributedSettings = PersistenceV2.connect(UserSettings, "settings", () => new UserSettings(), { distributed: true }
)
save方法:手动持久化数据
static save<T>(keyOrType: string | TypeConstructorWithArgs<T>): void;
参数说明:
keyOrType:需要手动持久化的key;如果指定的是type类型,key为type的name
返回值: 无
说明:
- 由于非@Trace的数据改变不会触发PersistenceV2的自动持久化,如有必要,可调用该接口持久化对应key的数据
- 手动持久化当前内存中不处于connect状态的key是无意义的
notifyOnError方法:响应序列化或反序列化失败的回调
static notifyOnError(callback: PersistenceErrorCallback | undefined): void;
参数说明:
callback:当序列化或者反序列化失败时,执行该回调;若传入undefined,取消该回调
返回值: 无
说明:
- 将数据存入磁盘时,需要对数据进行序列化;当某个key序列化失败时,错误是不可预知的;可调用该接口捕获异常
使用示例:
PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {console.error(`持久化错误 - key: ${key}, reason: ${reason}, message: ${msg}`);
});
globalConnect方法(API 18+)
static globalConnect<T extends object>(type: TypeConstructorWithArgs<T>,keyOrDefaultCreator?: string | StorageDefaultCreator<T>,defaultCreator?: StorageDefaultCreator<T>
): T | undefined;
功能: 创建或获取应用级别的持久化数据,跨模块共享
与connect的区别:
connect:模块级别存储,不同模块使用相同key会创建独立副本globalConnect:应用级别存储,跨模块共享同一份数据
2.3 持久化规则与限制
自动持久化规则
- @Trace属性:修改时自动触发持久化
- 非@Trace属性:修改后不会自动持久化,需要手动调用
save()
支持的持久化类型
- 基本类型:string、number、boolean、enum等
- 复合类型:通过@Type装饰的嵌套对象
- 数组类型:通过@Type装饰的数组
不支持的持久化类型
- Map/Set:不支持直接持久化,需要转换为数组
- 循环引用:不支持包含循环引用的对象
- 非JSON兼容类型:如Date、Symbol、undefined等
- any类型:禁止使用,无法保证序列化安全
使用限制
- 仅UI线程可用:需要配合UI使用(UI线程),不能在其他线程使用,如不支持@Sendable
- 特殊类型限制:不支持collections.Set、collections.Map等集合类型
- 非内置类型限制:不支持非buildin类型,如PixelMap、NativePointer、ArrayList等Native类型
- 数据大小限制:单个key支持数据大小约8k,过大会导致持久化失败
- 对象类型限制:持久化的数据必须是class对象,不能是容器(如Array、Set、Map),不能是buildin的构造对象(如Date、Number)
- 循环引用限制:不支持循环引用的对象
- 自动持久化触发条件:只有@Trace的数据改变会触发自动持久化,如V1状态变量、@Observed对象、普通数据的改变不会触发持久化
- 性能考虑:不宜大量持久化数据,可能会导致页面卡顿
2.4 实际应用场景
用户偏好设置持久化
@ObservedV2
class ThemeConfig {@Trace primaryColor: string = '#FF6200';@Trace isDarkMode: boolean = false;
}// 持久化存储主题配置
const theme = PersistenceV2.connect(ThemeConfig, 'userTheme', () => new ThemeConfig()
);// 在组件中使用
@ComponentV2
struct SettingsPage {@Consumer theme: ThemeConfig;build() {Column() {Text('主题颜色: ' + this.theme.primaryColor)Toggle({ isOn: this.theme.isDarkMode }).onChange((value) => {this.theme.isDarkMode = value; // 自动持久化})}}
}
登录状态持久化
@ObservedV2
class UserSession {@Trace isLoggedIn: boolean = false;@Trace userId: string = '';
}// 持久化存储登录状态
const session = PersistenceV2.connect(UserSession, 'loginSession', () => new UserSession()
);// 登录处理
function handleLogin(userId: string) {session.isLoggedIn = true;session.userId = userId; // 自动持久化
}// 登出处理
function handleLogout() {session.isLoggedIn = false;session.userId = ''; // 自动持久化
}
三、AppStorageV2与PersistenceV2的配合使用
3.1 数据流关系
UI组件 <--> AppStorageV2(内存) <--> PersistenceV2(磁盘)
3.2 典型应用模式
在实际应用中,我们通常会结合使用这两个组件来实现完整的状态管理方案:
@Entry
@ComponentV2
struct TodoList {// 临时设置(使用AppStorageV2)@Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;// 持久化任务列表(使用PersistenceV2)@Local taskList: TaskList = PersistenceV2.connect(TaskList, 'TaskList', () => new TaskList([]))!;build() {Column() {// 显示/隐藏已完成任务开关Toggle({type: ToggleType.Switch, isOn: this.setting.showCompletedTask}).onChange((isOn) => {this.setting.showCompletedTask = isOn;})// 任务列表ForEach(this.taskList.tasks, (task: Task) => {if (this.setting.showCompletedTask || !task.isFinish) {TaskItem({ task: task })}})}}
}
3.3 多设备同步配置
对于需要在多设备间同步的数据,可以启用分布式功能:
// 启用多设备同步的数据
const sharedData = AppStorageV2.connect(SharedClass, 'sharedKey', () => new SharedClass(), { distributed: true } // 关键配置
);
四、从V1到V2的迁移指南
4.1 核心装饰器对应关系
| V1装饰器 | V2装饰器 | 说明 |
|---|---|---|
| @State | @Local | 功能类似,@Local禁止外部初始化 |
| @Provide/@Consume | @Provider/@Consumer | 基本兼容,alias规则有变化 |
| @LocalStorage | 全局@ObservedV2/@Trace | 使用类替代LocalStorage实例 |
| PersistentStorage | PersistenceV2 | 功能更强大,可独立使用 |
4.2 迁移示例
V1版本代码:
@Entry
@Component
struct Parent {@State parentFruit: Fruit = new Fruit();build() {Child({ fruit: this.parentFruit })}
}
V2迁移后代码:
@ObservedV2
class Fruit {@Trace apple: number = 5;@Trace orange: number = 10;clone(): Fruit {let newFruit = new Fruit();newFruit.apple = this.apple;newFruit.orange = this.orange;return newFruit;}
}@Entry
@ComponentV2
struct Parent {@Local fruit: Fruit = new Fruit();build() {Child({ fruit: this.fruit.clone() })}
}
五、最佳实践与注意事项
5.1 最佳实践
- 合理设计数据结构:避免持久化大型数据集或频繁变化的变量
- 最小化共享范围:根据需求选择最小范围的共享方案
- 类型安全:确保存储的数据类型与使用时一致
- 错误处理:实现PersistenceV2的错误回调以处理持久化失败情况
- 性能优化:对频繁修改但不需要UI刷新的属性,避免使用@Trace装饰
5.2 注意事项
- 线程安全:AppStorageV2和PersistenceV2只能在UI线程中使用
- 数据大小:避免存储过大的数据,会影响性能
- 敏感信息:不要直接存储敏感信息(如密码、Token),建议加密存储
- 内存管理:及时清理不需要的临时数据,避免内存占用过高
- 错误恢复:实现适当的错误恢复机制,防止应用崩溃
六、总结
AppStorageV2和PersistenceV2作为鸿蒙API 12推出的新一代状态管理方案,通过提供更精细的状态控制、更灵活的持久化机制和更强大的跨组件数据共享能力,极大地简化了鸿蒙应用的状态管理复杂性。
通过合理运用这些组件,开发者可以构建出状态管理清晰、性能优化良好、用户体验流畅的鸿蒙应用。随着鸿蒙生态的不断发展,这些状态管理工具也将持续完善,为开发者提供更强大的支持。
其他资源
官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V14/arkts-new-appstoragev2-V14
