这是一个使用鸿蒙技术开发的本地原生记账应用,非常适合大家用来练手。相关源码已上传至 Github,点击此处查看项目。欢迎大家交流、指正,也欢迎提交 PR。

一、引言

在鸿蒙应用开发中,数据持久化是构建完整应用体验的关键一环。无论是保存用户的登录状态、个人偏好,还是缓存应用的核心数据,都需要一套可靠且高效的持久化方案。随着HarmonyOS版本的演进,数据持久化能力也从V1版本进化到了V2版本,带来了更强大的功能和更灵活的使用方式。

本文将深入对比V1(PersistentStorage + AppStorage)和V2(PersistenceV2 + @ObservedV2 + @Trace)两种数据持久化方案,并通过一个“登录与个人中心”示例,带你掌握它们的核心用法、适用场景以及最佳实践。

二、V1版本:PersistentStorage + AppStorage

2.1 简介

V1版本的持久化方案基于 PersistentStorageAppStorage 的配合使用。PersistentStorage 负责将选定的 AppStorage 属性持久化到磁盘,并在应用重启时自动恢复。UI和业务逻辑不直接访问 PersistentStorage,所有属性访问都通过 AppStorage 进行,两者之间是双向同步的关系。

2.2 登录与个人中心示例

下面通过一个登录与个人中心的示例,展示V1版本的具体用法。

1. 初始化持久化数据

EntryAbility 中,初始化需要持久化的属性。

// 在UI实例初始化后调用
onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
     // PersistentStorage必须在UI实例初始化成功后(loadContent回调中)调用
     // 早于此时机(onCreate/aboutToAppear)会导致持久化失效
     PersistentStorage.persistProp('isLogin', false);
     PersistentStorage.persistProp('userName', '');
     PersistentStorage.persistProp('token', '');
   });
 }

2. 登录页面 - 保存登录状态

使用 @StorageLink 装饰器将UI变量与 AppStorage 中的属性绑定,修改UI变量会自动同步到 PersistentStorage

import { promptAction, router } from '@kit.ArkUI';

@Entry
@Component
struct LoginPage {
  @StorageLink('isLogin') isLogin: boolean = false;
  @StorageLink('userName') userName: string = '';
  @StorageLink('token') token: string = '';

  build() {
    Column({space: 20}) {
      Text('当前登录状态:' + this.isLogin)
        .fontSize(20)
      Button('个人中心')
        .width('80%')
        .onClick(() => {
          if (this.isLogin) {
            router.push({
              url: 'pages/ProfilePage'
            })
          } else {
            promptAction.showToast({
              message: '未登录,请先登录'
            })
          }
        })

      Button('登录')
        .width('80%')
        .onClick(() => {
          // 模拟登录成功
          this.isLogin = true;
          this.userName = '张三';
          this.token = 'abc123token';
          // 数据自动同步到PersistentStorage持久化

          router.push({
            url: 'pages/ProfilePage'
          })
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

运行效果:

在这里插入图片描述

3. 个人中心页面 - 读取持久化数据

同样使用 @StorageLink 读取持久化的数据,并在UI中展示。

import { router } from '@kit.ArkUI';

@Entry
@Component
struct ProfilePage {
  @StorageLink('isLogin') isLogin: boolean = false;
  @StorageLink('userName') userName: string = '';
  @StorageLink('token') token: string = '';

  build() {
    Column({space: 20}) {
      if (this.isLogin) {
        Text(`用户名: ${this.userName}`)
          .fontSize(20)
        Text(`Token: ${this.token}`)
          .fontSize(20)
      } else {
        Text('未登录')
          .fontSize(20)
      }

      Button('返回')
        .width('80%')
        .onClick(() => {
        router.back()
      })
    }
    .width('100%')
    .height('100%')
  }
}

运行效果:

在这里插入图片描述

2.3 注意事项

  • 调用时机PersistentStorage 必须在UI实例初始化完成后调用,否则持久化可能失败。
  • 类型限制:仅支持 numberstringbooleanenumMapSetDateundefinednull,不支持嵌套对象。
  • 性能考量:避免持久化大型数据集和频繁变化的变量,否则会影响性能。
  • 存储路径:存储路径为module级别,不同module使用相同key时,数据归属最先使用的module。

三、V2版本:PersistenceV2 + @ObservedV2 + @Trace

3.1 简介

V2版本引入了 PersistenceV2 单例对象,配合 @ObservedV2@Trace 装饰器,实现了更强大、更灵活的持久化能力。通过 connectglobalConnect 绑定key,修改被 @Trace 装饰的属性时会自动触发持久化和UI更新。globalConnect 从API 18开始支持,存储路径为应用级别,是推荐的使用方式。

3.2 登录与个人中心示例

1. 定义持久化数据模型

使用 @ObservedV2 装饰类,并在需要持久化的属性上使用 @Trace 装饰器。

// src/main/ets/model/UserInfo.ets
@ObservedV2
export class UserInfo {
  @Trace isLogin: boolean = false;
  @Trace userName: string = '';
  @Trace token: string = '';
  // 普通属性不会触发自动持久化
  lastLoginTime: string = '';
}

2. 登录页面 - 保存登录状态

使用 @LocalPersistenceV2.connect 获取持久化数据实例,直接修改 @Trace 属性即可。

import { PersistenceV2 } from '@kit.ArkUI';
import { UserInfo } from '../model/UserInfo';
import router from '@ohos.router';
import promptAction from '@ohos.promptAction';

@Entry
@ComponentV2
struct LoginPage {
  @Local userInfo: UserInfo =
    PersistenceV2.connect(UserInfo, 'userInfoKey', () => new UserInfo())!;

  build() {
    Column({space: 20}) {
      Text('当前登录状态:' + this.userInfo.isLogin)
        .fontSize(20)

      Button('个人中心')
        .width('80%')
        .onClick(() => {
        if (this.userInfo.isLogin) {
          router.push({
            url: 'pages/ProfilePage'
          })
        } else {
          promptAction.showToast({ message: '未登录,请先登录' })
        }
      })
      Button('登录')
        .width('80%')
        .onClick(() => {
          // 直接修改@Trace属性,自动触发持久化和UI更新
          this.userInfo.isLogin = true;
          this.userInfo.userName = '张三';
          this.userInfo.token = 'abc123token';
          // 注意:lastLoginTime是普通属性,不会自动持久化

          router.push({
            url: 'pages/ProfilePage'
          })
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

运行效果:

在这里插入图片描述

3. 个人中心页面 - 读取持久化数据

同样使用 PersistenceV2.connect 获取数据实例,UI会自动响应 @Trace 属性的变化。

import { PersistenceV2 } from '@kit.ArkUI';
import { UserInfo } from '../model/UserInfo';
import router from '@ohos.router';

@Entry
@ComponentV2
struct ProfilePage {
  @Local userInfo: UserInfo =
    PersistenceV2.connect(UserInfo, 'userInfoKey', () => new UserInfo())!;

  build() {
    Column({space: 20}) {
      if (this.userInfo.isLogin) {
        Text(`用户名: ${this.userInfo.userName}`)
          .fontSize(20)
        Text(`Token: ${this.userInfo.token}`)
          .fontSize(20)
      } else {
        Text('未登录')
          .fontSize(20)
      }

      Button('返回')
        .width('80%')
        .onClick(() => {
        router.back()
      })
    }
    .width('100%')
    .height('100%')
  }
}

运行效果:

在这里插入图片描述

4. 整体赋值场景

当需要从服务器获取完整用户信息并整体赋值时,需要注意V2的机制。

// 方式一:逐个属性赋值(推荐)
const serverData = { isLogin: true, userName: '李四', token: 'newToken' };
this.userInfo.isLogin = serverData.isLogin;
this.userInfo.userName = serverData.userName;
this.userInfo.token = serverData.token;

// 方式二:先删除再重新连接(适用于大量属性)
PersistenceV2.remove('userInfoKey');
const newUserInfo = new UserInfo();
newUserInfo.isLogin = true;
newUserInfo.userName = '李四';
newUserInfo.token = 'newToken';
// 重新connect获取新实例

3.3 注意事项

  • @Trace装饰器:只有被 @Trace 装饰的属性变更才会触发自动持久化,普通属性、V1状态变量、@Observed 对象的变化不会触发。
  • 整体赋值:直接用新对象覆盖会导致持久化失效,需要逐个属性赋值或先删除再重新 connect
  • 数据量控制:不宜大量持久化数据,可能导致页面卡顿。
  • 调用时机:持久化操作需在UI实例初始化完成后调用(loadContent 回调触发后)。
  • API版本PersistenceV2 从API 12开始支持,globalConnect 从API 18开始支持,建议使用 globalConnect(应用级存储路径)。

四、V1与V2核心区别总结

对比项 V1 (PersistentStorage) V2 (PersistenceV2)
数据模型 仅支持基本类型,不支持嵌套对象 支持复杂对象(需 @ObservedV2 + @Trace
触发持久化 通过 AppStorage 属性变更自动同步 @Trace 属性变更触发自动持久化
存储路径 module级别 connect 为module级,globalConnect 为应用级
整体赋值 直接赋值即可 需逐个属性赋值或先删后建
适用场景 简单键值对存储 复杂UI状态持久化
性能 适合小数据量 不宜大量数据,避免卡顿

五、总结与选择建议

  • V1方案:简单、直接,适合存储登录状态、用户偏好等简单的键值对数据。如果你的数据结构不复杂,且不需要跨module共享,V1是一个轻量级的选择。
  • V2方案:功能强大,支持复杂对象和更精细的持久化控制。当你需要持久化复杂的UI状态、多页面共享状态,或者希望获得更灵活的存储路径(应用级)时,推荐使用V2的 globalConnect

选择哪种方案取决于你的具体需求。对于大多数现代鸿蒙应用,尤其是涉及复杂数据交互的场景,V2方案是更优的选择。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐