PersistentStorage:持久化存储 UI 状态使用指南

效果

一、概述

PersistentStorage 是 HarmonyOS 应用中的可选单例对象,用于将选定的 AppStorage 属性持久化存储到设备磁盘。其核心价值在于:应用重启后,被持久化的状态变量能够自动恢复到上一次关闭时的值

1.1 核心特性

  • 自动持久化:数据变更时自动同步写入磁盘,无需手动调用保存方法
  • 与 AppStorage 深度集成:通过 @StorageLink@StorageProp 实现双向/单向数据绑定
  • 轻量便捷:适合存储小型 UI 状态数据(建议小于 2KB)

1.2 适用场景

场景 说明
登录状态记忆 记住用户是否已登录、登录昵称、手机号、是否保持登录
主题切换 保存深色/浅色模式偏好
页面标签索引 记住用户当前选中的 Tab 页
简单配置项 字体大小、语言设置等
搜索历史关键词 保存最近的搜索词

1.3 不适用场景

  • 大量结构化数据(建议使用关系型数据库 RDB)
  • 高频变化的数据(如定时器计数、动画帧数据)
  • 复杂对象或大数组(超过 2KB 会影响 UI 性能)

二、核心概念与工作原理

2.1 数据流向

UI 组件 ←→ AppStorage ←→ PersistentStorage ←→ 磁盘文件
  1. PersistentStorage 将指定 key 注册为持久化属性
  2. 该属性同步到 AppStorage
  3. UI 组件通过 @StorageLink / @StorageProp 绑定该属性
  4. 属性值变化时,自动写入磁盘;应用重启时,自动从磁盘读回

2.2 关键 API

方法 说明
persistProp(key, defaultValue) 持久化单个属性,若已有值则保留,否则使用默认值
persistProps(obj) 批量持久化多个属性
deleteProp(key) 删除持久化属性(不删除 AppStorage 中的值)
clear() 清除所有持久化属性

2.3 初始化时机

PersistentStorage 必须在 UI 实例初始化成功后才能调用,即在 loadContent 的回调中调用:

// EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) return;
    // 正确时机:loadContent 回调中
    PersistentStorage.persistProp('isDarkMode', false);
  });
}

注意:如果在 loadContent 之前调用,会导致持久化失败。


三、基础用法

3.1 持久化单个属性(persistProp)

步骤一:在 EntryAbility 中初始化持久化属性

// EntryAbility.ets
windowStage.loadContent('pages/GlowHomePage', (err) => {
  if (err.code) return;
  // 静默登录开关(仅记录用户意愿)
  PersistentStorage.persistProp('silentLoginEnabled', false);
  PersistentStorage.persistProp('lastPhone', '');
  // 登录状态(跨页面实时同步)
  PersistentStorage.persistProp('isLoggedIn', false);
  PersistentStorage.persistProp('loggedInNickname', '');
});

步骤二:登录页写入登录状态(@StorageLink 双向绑定)

// GlowLoginPage.ets
@StorageLink('isLoggedIn') isLoggedIn: boolean = false;
@StorageLink('loggedInNickname') loggedInNickname: string = '';
@StorageLink('lastPhone') lastPhone: string = '';

async handleLogin(): Promise<void> {
  // ... 查询/注册成功后
  this.isLoggedIn = true;                     // 主页立即响应“已登录”
  this.loggedInNickname = existingUser.nickname; // 主页立即显示昵称
  this.lastPhone = this.phoneInput.trim();
}

步骤三:首页读取登录状态并支持退出

// GlowHomePage.ets
@StorageLink('isLoggedIn') isLoggedIn: boolean = false;
@StorageLink('loggedInNickname') loggedInNickname: string = '';
@StorageLink('lastPhone') lastPhone: string = '';

logout(): void {
  this.isLoggedIn = false;
  this.loggedInNickname = '';
  this.lastPhone = '';
}

// UI 中使用
Text(this.isLoggedIn ? `Hi, ${this.loggedInNickname}` : '探索无限可能')

关键点isLoggedInloggedInNickname 使用 @StorageLink 双向绑定,登录页写入后主页无需刷新即可实时响应。

@Component
struct SettingsPage {
  @StorageLink('isRemembered') isRemembered: boolean = false;
  @StorageLink('themeColor') themeColor: string = 'blue';

  build() {
    Column() {
      Toggle({ type: ToggleType.Switch, isOn: this.isRemembered })
        .onChange((isOn: boolean) => {
          this.isRemembered = isOn;  // 自动持久化
        })

      Text(`当前主题:${this.themeColor}`)
        .onClick(() => {
          this.themeColor = this.themeColor === 'blue' ? 'green' : 'blue';
        })
    }
  }
}

@StorageLink 实现双向绑定:组件内修改值会自动同步到 AppStorage,并触发 PersistentStorage 持久化写入。

3.2 单向读取(@StorageProp)

如果只需要读取值,不需要反向同步,使用 @StorageProp

@Component
struct DisplayPage {
  @StorageProp('isRemembered') isRemembered: boolean = false;

  build() {
    Text(this.isRemembered ? '已记住登录状态' : '未记住登录状态')
  }
}

@StorageProp 是单向绑定:AppStorage 变化会同步到组件,但组件内修改不会回写。

3.3 批量持久化(persistProps)

一次性持久化多个属性:

PersistentStorage.persistProps({
  'fontSize': 16,
  'language': 'zh-CN',
  'isNotificationOn': true
});

3.4 访问与修改

通过 AppStorage 的 API 可以直接访问和修改持久化属性:

// 读取
let value = AppStorage.get<boolean>('isRemembered');

// 修改(自动触发持久化)
AppStorage.set<boolean>('isRemembered', true);

// 不存在则创建
AppStorage.setOrCreate('newKey', 'hello');

四、完整示例:搜索历史关键词持久化

以下示例展示一个搜索页面,使用 PersistentStorage 保存用户的搜索历史关键词(最近 5 条)。

4.1 EntryAbility 初始化

// EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {}

  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    windowStage.loadContent('pages/SearchPage', (err) => {
      if (err.code) return;
      // 持久化搜索历史,默认为空数组
      PersistentStorage.persistProp('searchHistory', [] as string[]);
    });
  }

  onDestroy(): void {}
  onForeground(): void {}
  onBackground(): void {}
}

4.2 搜索页面组件

// SearchPage.ets
@Entry
@Component
struct SearchPage {
  @State searchText: string = '';
  @StorageLink('searchHistory') searchHistory: string[] = [];

  addSearchHistory(keyword: string): void {
    // 去除重复
    const index = this.searchHistory.indexOf(keyword);
    if (index > -1) {
      this.searchHistory.splice(index, 1);
    }
    // 插入到最前面
    this.searchHistory.unshift(keyword);
    // 只保留最近 5 条
    if (this.searchHistory.length > 5) {
      this.searchHistory.pop();
    }
  }

  clearHistory(): void {
    this.searchHistory = [];
  }

  build() {
    Column() {
      // 搜索栏
      Row() {
        TextInput({ placeholder: '搜索...' })
          .onChange((value: string) => { this.searchText = value; })
          .layoutWeight(1)
          .height(40)

        Button('搜索')
          .onClick(() => {
            if (this.searchText.trim() !== '') {
              this.addSearchHistory(this.searchText.trim());
              this.searchText = '';
            }
          })
          .margin({ left: 10 })
      }
      .width('90%')
      .padding({ top: 20 })

      // 搜索历史
      if (this.searchHistory.length > 0) {
        Row() {
          Text('搜索历史')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .layoutWeight(1)
          Text('清空')
            .fontSize(14)
            .fontColor('#999999')
            .onClick(() => { this.clearHistory(); })
        }
        .width('90%')
        .margin({ top: 20 })

        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.searchHistory, (item: string) => {
            Text(item)
              .fontSize(14)
              .padding({ left: 12, right: 12, top: 6, bottom: 6 })
              .backgroundColor('#f0f0f0')
              .borderRadius(16)
              .margin({ right: 8, top: 8 })
              .onClick(() => {
                this.searchText = item;
              })
          })
        }
        .width('90%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

4.3 运行效果说明

  1. 首次运行:搜索历史为空,输入关键词点击搜索后,关键词被添加到历史列表
  2. 退出应用:搜索历史自动持久化到磁盘
  3. 重新启动:搜索历史自动恢复,显示之前保存的关键词
  4. 最多保留 5 条:新搜索词插入到最前面,超过 5 条时自动移除最旧的

五、进阶示例:深色模式切换

// EntryAbility.ets
windowStage.loadContent('pages/ThemePage', (err) => {
  if (err.code) return;
  PersistentStorage.persistProp('isDarkMode', false);
});
// ThemePage.ets
@Entry
@Component
struct ThemePage {
  @StorageLink('isDarkMode') isDarkMode: boolean = false;

  build() {
    Column() {
      Row() {
        Text('深色模式')
          .fontSize(18)
          .layoutWeight(1)
        Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
          .selectedColor('#007DFF')
          .onChange((isOn: boolean) => {
            this.isDarkMode = isOn;
          })
      }
      .width('80%')
      .padding(20)

      Column() {
        Text('这是一段示例文本')
          .fontSize(16)
          .fontColor(this.isDarkMode ? '#ffffff' : '#000000')
        Text('当前为' + (this.isDarkMode ? '深色' : '浅色') + '模式')
          .fontSize(14)
          .fontColor(this.isDarkMode ? '#aaaaaa' : '#666666')
          .margin({ top: 10 })
      }
      .width('80%')
      .padding(20)
      .backgroundColor(this.isDarkMode ? '#333333' : '#f5f5f5')
      .borderRadius(12)
      .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.isDarkMode ? '#1a1a1a' : '#ffffff')
  }
}

六、注意事项与最佳实践

6.1 性能注意

要点 说明
数据大小 单个属性建议小于 2KB,大量数据请使用 RDB
写入时机 写入磁盘在 UI 线程同步执行,大对象会阻塞渲染
高频变更 避免持久化频繁变化的数据(如动画帧、定时器)

6.2 初始化顺序

1. loadContent 加载页面
2. 在 loadContent 回调中调用 PersistentStorage.persistProp()
3. 组件中使用 @StorageLink / @StorageProp 绑定

重要:如果先在 AppStorage 中创建了同名属性,再调用 PersistentStorage.persistProp,会使用 AppStorage 中已有的值,而非磁盘中的值。建议先调用 PersistentStorage

6.3 数据类型支持

PersistentStorage 支持以下数据类型:

  • 基础类型:stringnumberboolean
  • 对象和数组(需可 JSON 序列化)
  • 不支持:MapSetDateRegExp 等复杂对象

6.4 常见误区

误区 正确做法
onCreate 中调用 persistProp 应在 loadContent 回调中调用
存储大对象或长数组 使用关系型数据库 RDB
在组件 aboutToAppear 中初始化持久化 初始化应在 Ability 的 loadContent 回调中
@StorageProp 尝试修改值 需修改时使用 @StorageLink
登录状态仅存本地 @State,跨页面不同步 使用 @StorageLink 存入 AppStorage,跨页面实时响应
只持久化 lastPhone,不持久化 isLoggedIn 同时持久化 isLoggedIn + loggedInNickname,避免重启后状态不一致

七、API 速查表

PersistentStorage 静态方法

方法 参数 说明
persistProp(key, defaultValue) key: string, defaultValue: T 持久化单个属性
persistProps(obj) obj: Object 批量持久化多个属性
deleteProp(key) key: string 删除持久化属性
clear() 清除所有持久化属性

组件内绑定装饰器

装饰器 方向 说明
@StorageLink(key) 双向 组件修改会同步到 AppStorage 并持久化
@StorageProp(key) 单向 只能读取 AppStorage 中的值

八、总结

PersistentStorage 是 HarmonyOS 中最轻量的状态持久化方案,核心优势是零代码自动持久化。只需三步即可使用:

  1. 初始化:在 loadContent 回调中调用 persistProp
  2. 绑定:组件中使用 @StorageLink 双向绑定
  3. 使用:正常读写状态变量,变更自动持久化

适合存储登录状态、主题偏好、Tab 索引等小型 UI 状态数据,是 HarmonyOS 状态管理体系中不可或缺的一环。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐