前言

在上一篇文章中,我们探讨了组件内部以及深层嵌套对象的状态管理。但当我们把视角拉高,俯瞰整个应用时,会发现一个新的挑战:数据孤岛。

用户在“我的”页面修改了头像,首页的左上角是不是也得跟着变?用户在“设置”里开启了夜间模式,是不是所有的页面都要瞬间切换颜色?如果只靠组件之间的父子传递(Props),我们需要把这些状态一路从根节点透传下去,这种属性钻取简直是代码维护的噩梦。

更棘手的是,当用户划掉后台进程,杀掉应用,再次打开时,我们希望他上次设置的“夜间模式”依然生效。这就涉及到了内存数据与磁盘数据的同步问题。

在鸿蒙 HarmonyOS 6 (API 20) 中,ArkUI 给出了一套完整的应用级状态管理方案:AppStorage全局内存、LocalStorage页面级内存以及 PersistentStorage持久化存储。

一、 AppStorage 应用内存状态的全局管理

AppStorage 是应用在内存中的全局状态容器。一旦在其中存入属性,应用内的任意 Ability、Page 或自定义组件均可访问和修改。

在声明式 UI 开发中,不建议在 build 函数中频繁直接调用 API。ArkUI 提供了两个专用的装饰器:

  • @StorageLink:建立双向同步。组件内状态的修改会同步至全局,并驱动其他订阅该状态的组件更新。
  • @StorageProp:建立单向订阅。仅接收全局状态的更新,组件内的修改不会同步回全局。

例如,将“当前用户 Token”存储在 AppStorage 中。在任意页面声明 @StorageLink('userToken'),该变量即可与全局状态联通。无论何处更新了 Token,所有页面的对应变量均会实时刷新。

二、 PersistentStorage 持久化数据同步

AppStorage 仅存在于内存中,应用退出后数据会被清除。PersistentStorage 的作用是将 AppStorage 中的特定属性写入磁盘文件,实现持久化。

PersistentStorage 并非独立的数据库,而是 AppStorage 与文件系统之间的同步机制。通常在应用启动早期(如 EntryAbility 或页面加载前)调用 PersistentStorage.persistProp('key', defaultValue)。系统会检查磁盘中是否存在该键值:若存在,则读取并覆盖 AppStorage 中的值;若不存在,则使用默认值初始化,并建立磁盘映射。

连接建立后,开发者只需通过 @StorageLink 修改 AppStorage 中的数据,框架会自动监听变化并将新值写入磁盘,无需手动处理文件读写或 JSON 序列化。

三、 LocalStorage 模块化状态隔离

与全局的 AppStorage 不同,LocalStorage 用于解决模块化和多实例场景下的状态共享问题(如多窗口应用或独立模块)。

LocalStorage 的生命周期绑定在 Ability 或 UIAbility 上下文中,创建了一个隔离的状态容器。在页面加载时,可传入独立的 LocalStorage 实例,组件内部则使用 @LocalStorageLink 进行对接。这种机制能有效保证状态的封装性与安全性,防止因全局变量过多导致的状态污染。

四、 最佳实践 全局主题切换功能实现

以下通过“全局主题切换”功能(支持深色模式与字体大小调整)演示三者的协同工作。需求包括:设置在所有页面生效,且应用重启后配置依然保留。

实现时需遵循严格的初始化顺序:先持久化,再 UI 绑定。务必在 Ability 或文件顶层执行 PersistentStorage.persistProp,切勿在组件的 build 函数中执行持久化初始化,以免引发逻辑错误。

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

// =============================================================
// 1. 初始化持久化数据 (必须在 @Entry 之前执行)
// =============================================================
// 这里的逻辑是:
// 1. 检查磁盘是否有 'appThemeMode'。
// 2. 如果有,将其加载到 AppStorage 中。
// 3. 如果没有,则使用默认值 'light' 初始化 AppStorage,并写入磁盘。
PersistentStorage.persistProp('appThemeMode', 'light');
PersistentStorage.persistProp('appFontSize', 16);

@Entry
@Component
struct GlobalStatePage {
  // =============================================================
  // 2. UI 绑定全局状态 (@StorageLink)
  // =============================================================
  // @StorageLink 与 AppStorage 建立双向同步:
  // 读取:初始化时从 AppStorage 获取值。
  // 写入:当 this.themeMode 改变 -> AppStorage 更新 -> PersistentStorage 写入磁盘。
  @StorageLink('appThemeMode') themeMode: string = 'light';
  @StorageLink('appFontSize') fontSize: number = 16;

  build() {
    // 根容器:背景色根据主题动态变化
    Column() {
      // --- 顶部标题栏 ---
      Text('全局设置中心')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        // 动态适配字体颜色
        .fontColor(this.themeMode === 'light' ? '#333333' : '#FFFFFF')
        .margin({ top: 40, bottom: 40 })

      // --- 设置选项卡片区域 ---
      Column({ space: 20 }) {

        // -----------------------------------------------------
        // 选项 1:字体大小调节
        // -----------------------------------------------------
        Row() {
          Text('全局字号')
            .fontSize(this.fontSize) // 【关键点】应用动态字号
            .fontColor(this.themeMode === 'light' ? '#333' : '#FFF')
            .fontWeight(FontWeight.Medium)

          // 调节按钮组
          Row() {
            Button('A-')
              .onClick(() => {
                if (this.fontSize > 12) {
                  this.fontSize -= 2; // 修改状态 -> 自动触发持久化保存
                } else {
                  promptAction.showToast({ message: '已经是最小字体了' })
                }
              })
              .backgroundColor(this.themeMode === 'light' ? '#F0F0F0' : '#444')
              .fontColor(this.themeMode === 'light' ? '#333' : '#FFF')
              .margin({ right: 10 })
              .width(40)
              .height(32)

            Button('A+')
              .onClick(() => {
                if (this.fontSize < 30) {
                  this.fontSize += 2; // 修改状态 -> 自动触发持久化保存
                } else {
                  promptAction.showToast({ message: '已经是最大字体了' })
                }
              })
              .backgroundColor(this.themeMode === 'light' ? '#F0F0F0' : '#444')
              .fontColor(this.themeMode === 'light' ? '#333' : '#FFF')
              .width(40)
              .height(32)
          }
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .padding(16)
        .backgroundColor(this.themeMode === 'light' ? '#FFFFFF' : '#2C2C2C')
        .borderRadius(16)
        .shadow({ radius: 8, color: this.themeMode === 'light' ? '#10000000' : '#00000000', offsetY: 2 })

        // -----------------------------------------------------
        // 选项 2:主题模式切换
        // -----------------------------------------------------
        Row() {
          Column() {
            Text('夜间模式')
              .fontSize(this.fontSize)
              .fontColor(this.themeMode === 'light' ? '#333' : '#FFF')
            Text(this.themeMode === 'light' ? '当前:日间' : '当前:深色')
              .fontSize(12)
              .fontColor('#888')
              .margin({ top: 4 })
          }
          .alignItems(HorizontalAlign.Start)

          // 开关组件
          Toggle({ type: ToggleType.Switch, isOn: this.themeMode === 'dark' })
            .selectedColor('#0A59F7')
            .onChange((isOn: boolean) => {
              // 【关键点】修改状态
              // 这一步会瞬间触发:
              // 1. 本页面 UI 刷新(背景变黑/白)
              // 2. AppStorage 全局变量更新
              // 3. 磁盘文件写入
              this.themeMode = isOn ? 'dark' : 'light';

              promptAction.showToast({
                message: `已切换至${isOn ? '深色' : '日间'}模式,设置已自动保存`,
                duration: 2000
              });
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .padding(16)
        .backgroundColor(this.themeMode === 'light' ? '#FFFFFF' : '#2C2C2C')
        .borderRadius(16)
        .shadow({ radius: 8, color: this.themeMode === 'light' ? '#10000000' : '#00000000', offsetY: 2 })

        // --- 底部提示 ---
        Text('提示:该设置已持久化存储。您可以尝试彻底关闭应用(杀后台),再次打开时,上述设置依然生效。')
          .fontSize(12)
          .fontColor('#999')
          .margin({ top: 20 })
          .padding({ left: 10, right: 10 })
          .textAlign(TextAlign.Center)
          .lineHeight(18)

      }
      .padding(16)
    }
    .width('100%')
    .height('100%')
    // 全局背景色
    .backgroundColor(this.themeMode === 'light' ? '#F1F3F5' : '#121212')
    // 添加过渡动画,让主题切换更丝滑
    .animation({ duration: 300, curve: Curve.EaseInOut })
  }
}

总结

掌握了 AppStoragePersistentStorage,你就掌握了鸿蒙应用数据流动的“任督二脉”。AppStorage 打通了页面间的隔阂,让数据在内存中自由穿梭;PersistentStorage 则打通了内存与磁盘的界限,让关键数据得以长久保存。在实际开发中,建议将所有的 Keys 定义为一个常量文件,避免魔术字符串满天飞。同时,虽然全局状态很好用,但也要克制,不要把所有数据都往里塞,只存放那些真正需要全局共享和持久化的“配置级”或“用户级”数据,保持应用内存的纯净与高效。

Logo

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

更多推荐