📖 鸿蒙NEXT开发实战系列 | 第18篇 | 进阶篇 🎯 适合人群:有鸿蒙基础状态管理经验的开发者 ⏰ 阅读时间:约12分钟 | 💻 开发环境:DevEco Studio 5.0+

上一篇:DevEco Studio必备工具清单 | 下一篇:待更新


目录


一、引言:三个Storage到底该怎么选?

在鸿蒙NEXT开发中,当你的应用规模逐渐增大,组件之间的状态共享需求会越来越多。这时你会发现,除了 @State@Link@Provide/@Consume 这些组件级状态装饰器之外,还有三个以 "Storage" 命名的方案:

  • AppStorage -- 应用级状态存储

  • LocalStorage -- 页面级状态存储

  • PersistentStorage -- 持久化存储

很多开发者在第一次接触时都会感到困惑:它们看起来都能存数据,到底有什么区别?什么时候该用哪个?

本文将通过对比表格 + 选型决策树 + 完整实战代码,帮你彻底理清这三种存储方案的定位和用法。


二、三种Storage全景对比

先上一张对比表,帮你建立全局认知:

对比维度

AppStorage

LocalStorage

PersistentStorage

作用域

整个应用进程

单个页面/组件树

整个应用进程

生命周期

应用存活期间,退出即清除

页面存活期间,页面销毁即清除

永久保存,重启应用后仍然存在

数据持久化

否,内存级

否,内存级

是,写入磁盘

典型用途

用户登录态、全局主题、语言设置

弹窗状态、页面内部配置

用户设置偏好、历史记录

UI联动装饰器

@StorageProp / @StorageLink

@LocalStorageProp / @LocalStorageLink

配合 @StorageProp / @StorageLink 使用

数据共享范围

任意页面和组件

当前页面及其子组件

任意页面和组件

API来源

@ohos.app.ets.AppStorage

构造函数传入

@ohos.app.ets.PersistentStorage

一句话总结

  • AppStorage = 应用级别的"全局变量",所有页面共享,应用退出就没了

  • LocalStorage = 页面级别的"私有变量",只在当前页面及其子组件中有效

  • PersistentStorage = 会写入磁盘的"全局变量",应用重启数据还在


三、AppStorage:应用级全局状态管理

3.1 核心概念

AppStorage 是鸿蒙提供的应用级状态存储,它在整个应用进程的生命周期内维护一个键值对存储。所有页面和组件都可以通过 @StorageProp@StorageLink 装饰器与之建立双向或单向的数据绑定。

适用场景:

  • 用户登录信息(token、用户昵称)

  • 全局主题模式(深色/浅色)

  • 全局语言设置

  • 全局配置参数

// EntryAbility.ets 中初始化AppStorage(可选,也可在页面中直接使用)
import { AppStorage } from '@kit.ArkUI';

// 在应用启动时设置初始值
AppStorage.setOrCreate<string>('appTheme', 'light');
AppStorage.setOrCreate<string>('userName', '游客');
AppStorage.setOrCreate<boolean>('isLoggedIn', false);
// pages/SettingsPage.ets
@Entry
@Component
struct SettingsPage {
  // @StorageLink:双向绑定,页面修改会同步回AppStorage
  @StorageLink('appTheme') appTheme: string = 'light';

  // @StorageProp:单向绑定,只从AppStorage读取,页面修改不会回写
  @StorageProp('userName') userName: string = '游客';

  build() {
    Column() {
      // 显示当前用户
      Text(`当前用户:${this.userName}`)
        .fontSize(20)
        .margin({ bottom: 20 })

      // 主题切换按钮
      Row() {
        Button('浅色模式')
          .backgroundColor(this.appTheme === 'light' ? '#1890FF' : '#E0E0E0')
          .onClick(() => {
            this.appTheme = 'light';  // 双向绑定,AppStorage同步更新
          })
          .margin({ right: 10 })

        Button('深色模式')
          .backgroundColor(this.appTheme === 'dark' ? '#1890FF' : '#E0E0E0')
          .onClick(() => {
            this.appTheme = 'dark';
          })
      }

      // 主题预览区域
      Text(`当前主题:${this.appTheme === 'light' ? '浅色' : '深色'}`)
        .fontSize(16)
        .margin({ top: 20 })
        .padding(20)
        .borderRadius(12)
        .backgroundColor(this.appTheme === 'light' ? '#FFFFFF' : '#333333')
        .fontColor(this.appTheme === 'light' ? '#333333' : '#FFFFFF')
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}
// pages/ProfilePage.ets -- 另一个页面,共享同一份AppStorage数据
@Entry
@Component
struct ProfilePage {
  // 从AppStorage读取同一个key,自动同步
  @StorageLink('appTheme') appTheme: string = 'light';
  @StorageProp('userName') userName: string = '游客';
  @StorageProp('isLoggedIn') isLoggedIn: boolean = false;

  build() {
    Column() {
      Text(`用户:${this.userName}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      Text(`状态:${this.isLoggedIn ? '已登录' : '未登录'}`)
        .fontSize(16)
        .margin({ top: 8 })

      Text(`主题:${this.appTheme === 'light' ? '浅色' : '深色'}`)
        .fontSize(16)
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor(this.appTheme === 'light' ? '#F5F5F5' : '#1A1A1A')
  }
}

对比项

@StorageLink

@StorageProp

绑定方向

双向绑定

单向绑定(只读)

页面修改是否回写

是,同步回AppStorage

否,仅修改本地副本

适用场景

需要从页面修改并同步的场景

只读展示,不需要回写的场景

3.4 在子组件中访问AppStorage

// 子组件中也可以直接使用@StorageLink/@StorageProp
@Component
struct ThemeCard {
  @StorageLink('appTheme') currentTheme: string = 'light';

  build() {
    Column() {
      Text('主题卡片组件')
        .fontSize(16)
        .fontColor(this.currentTheme === 'light' ? '#333' : '#FFF')

      Button('切换主题')
        .onClick(() => {
          this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
        })
    }
    .padding(16)
    .borderRadius(12)
    .backgroundColor(this.currentTheme === 'light' ? '#FFF' : '#444')
  }
}

四、LocalStorage:页面级私有状态管理

4.1 核心概念

LocalStorage页面级的状态存储,它的作用域仅限于创建它的页面及其子组件树。当页面被销毁时,LocalStorage 中的数据也会随之清除。

适用场景:

  • 页面内部多个组件共享的状态(如弹窗显隐、筛选条件)

  • 模态框的参数传递

  • 页面内部的配置数据(如列表排序方式)

4.2 基础用法:创建和注入

// pages/ProductFilterPage.ets

// 1. 定义LocalStorage的初始数据
const productFilterStorage = new LocalStorage({
  'filterCategory': '全部',
  'sortBy': '销量',
  'priceRange': '0-1000',
  'isShowFilter': false
});

// 2. 使用@Entry和@LocalStorageLink获取页面的LocalStorage实例
@Entry(productFilterStorage)  // 将LocalStorage注入到页面
@Component
struct ProductFilterPage {
  // 3. 使用@LocalStorageLink双向绑定
  @LocalStorageLink('filterCategory') filterCategory: string = '全部';
  @LocalStorageLink('sortBy') sortBy: string = '销量';
  @LocalStorageLink('isShowFilter') isShowFilter: boolean = false;

  build() {
    Column() {
      // 顶部筛选栏
      Row() {
        Text(`分类:${this.filterCategory}`)
          .fontSize(14)
          .fontColor('#666')
          .padding(8)
          .backgroundColor('#F0F0F0')
          .borderRadius(16)
          .margin({ right: 8 })

        Text(`排序:${this.sortBy}`)
          .fontSize(14)
          .fontColor('#666')
          .padding(8)
          .backgroundColor('#F0F0F0')
          .borderRadius(16)

        Blank()

        Button('筛选')
          .fontSize(14)
          .height(32)
          .onClick(() => {
            this.isShowFilter = !this.isShowFilter;
          })
      }
      .width('100%')
      .padding(12)

      // 商品列表区域
      List({ space: 8 }) {
        ForEach(['商品A', '商品B', '商品C', '商品D'], (item: string) => {
          ListItem() {
            Text(item)
              .fontSize(16)
              .width('100%')
              .padding(16)
              .backgroundColor(Color.White)
              .borderRadius(8)
          }
        })
      }
      .layoutWeight(1)
      .padding({ left: 12, right: 12 })

      // 条件渲染筛选面板(使用LocalStorage的状态控制)
      if (this.isShowFilter) {
        FilterPanel()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

// 4. 子组件通过构造函数传入LocalStorage实例
@Component
struct FilterPanel {
  // 在子组件中使用@LocalStorageLink
  @LocalStorageLink('filterCategory') filterCategory: string = '全部';
  @LocalStorageLink('sortBy') sortBy: string = '销量';
  @LocalStorageLink('isShowFilter') isShowFilter: boolean = false;

  private categories: string[] = ['全部', '手机', '电脑', '服饰', '家居'];
  private sortOptions: string[] = ['销量', '价格升序', '价格降序', '最新'];

  build() {
    Column() {
      // 标题
      Row() {
        Text('筛选条件')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)

        Blank()

        Text('关闭')
          .fontSize(14)
          .fontColor('#FF4D4F')
          .onClick(() => {
            this.isShowFilter = false;
          })
      }
      .width('100%')
      .padding({ bottom: 12 })

      // 分类选择
      Text('商品分类')
        .fontSize(14)
        .fontColor('#666')
        .margin({ bottom: 8 })

      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.categories, (item: string) => {
          Text(item)
            .fontSize(14)
            .padding({ left: 16, right: 16, top: 8, bottom: 8 })
            .backgroundColor(this.filterCategory === item ? '#1890FF' : '#F0F0F0')
            .fontColor(this.filterCategory === item ? '#FFF' : '#333')
            .borderRadius(16)
            .margin({ right: 8, bottom: 8 })
            .onClick(() => {
              this.filterCategory = item;
            })
        })
      }

      // 排序选择
      Text('排序方式')
        .fontSize(14)
        .fontColor('#666')
        .margin({ top: 12, bottom: 8 })

      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.sortOptions, (item: string) => {
          Text(item)
            .fontSize(14)
            .padding({ left: 16, right: 16, top: 8, bottom: 8 })
            .backgroundColor(this.sortBy === item ? '#1890FF' : '#F0F0F0')
            .fontColor(this.sortBy === item ? '#FFF' : '#333')
            .borderRadius(16)
            .margin({ right: 8, bottom: 8 })
            .onClick(() => {
              this.sortBy = item;
            })
        })
      }
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius({ topLeft: 16, topRight: 16 })
    .shadow({ radius: 8, color: '#1A000000', offsetY: -4 })
  }
}

4.3 AppStorage vs LocalStorage 核心区别

对比维度

AppStorage

LocalStorage

数据存储位置

应用进程级内存

页面实例级内存

页面间共享

可以,任意页面访问同一个key

不可以,仅限当前页面

初始化方式

AppStorage.setOrCreate()

new LocalStorage({...})

注入方式

自动全局可访问

@Entry(storage) 或通过构造函数

典型场景

用户信息、主题、语言

页面内筛选条件、弹窗状态


五、PersistentStorage:持久化存储

5.1 核心概念

PersistentStorage 的核心能力是:将AppStorage中的指定key持久化到磁盘。应用退出后再次启动,这些数据会自动恢复到AppStorage中。

它本质上是AppStorage的"持久化增强层",而不是一个独立的存储系统。

适用场景:

  • 用户的主题偏好设置(深色/浅色模式)

  • 用户的语言选择

  • 上次阅读位置

  • 免登录token

5.2 基础用法

// EntryAbility.ets 或 Index.ets 中初始化(必须在UI加载之前调用)
import { PersistentStorage, AppStorage } from '@kit.ArkUI';

// 将指定key持久化 -- 必须在@Entry组件创建之前调用
PersistentStorage.persistProp('appTheme', 'light');      // 持久化主题设置
PersistentStorage.persistProp('language', 'zh-CN');       // 持久化语言设置
PersistentStorage.persistProp('fontSize', 16);            // 持久化字体大小
PersistentStorage.persistProp('isFirstLaunch', true);     // 持久化首次启动标记

// 此时AppStorage中已经自动包含了这些持久化数据
// 应用重启后,这些值会从磁盘自动恢复
// pages/UserPreferences.ets
@Entry
@Component
struct UserPreferences {
  // 持久化的数据,应用重启后自动恢复
  @StorageLink('appTheme') appTheme: string = 'light';
  @StorageLink('language') language: string = 'zh-CN';
  @StorageLink('fontSize') fontSize: number = 16;

  build() {
    Column() {
      Text('用户偏好设置')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })

      // 主题设置
      this.SettingRow('深色模式', this.appTheme === 'dark', (value: boolean) => {
        this.appTheme = value ? 'dark' : 'light';
      })

      // 语言设置
      this.SettingRow('English', this.language === 'en-US', (value: boolean) => {
        this.language = value ? 'en-US' : 'zh-CN';
      })

      // 字体大小设置
      Column() {
        Text(`字体大小:${this.fontSize}sp`)
          .fontSize(16)
          .margin({ bottom: 10 })

        Row() {
          Button('A-')
            .fontSize(14)
            .width(50)
            .onClick(() => {
              if (this.fontSize > 12) {
                this.fontSize -= 2;
              }
            })

          Slider({
            value: this.fontSize,
            min: 12,
            max: 24,
            step: 2
          })
            .layoutWeight(1)
            .margin({ left: 10, right: 10 })
            .onChange((value: number) => {
              this.fontSize = value;
            })

          Button('A+')
            .fontSize(18)
            .width(50)
            .onClick(() => {
              if (this.fontSize < 24) {
                this.fontSize += 2;
              }
            })
        }
        .width('100%')
      }
      .width('100%')
      .padding(16)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .margin({ top: 20 })

      // 提示信息
      Text('以上设置会在应用重启后自动恢复')
        .fontSize(12)
        .fontColor('#999')
        .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor(this.appTheme === 'dark' ? '#1A1A1A' : '#F5F5F5')
  }

  @Builder
  SettingRow(label: string, value: boolean, onChange: (value: boolean) => void) {
    Row() {
      Text(label)
        .fontSize(16)
        .fontColor(this.appTheme === 'dark' ? '#FFF' : '#333')

      Blank()

      Toggle({ type: ToggleType.Switch, isOn: value })
        .onChange(onChange)
    }
    .width('100%')
    .padding(16)
    .backgroundColor(this.appTheme === 'dark' ? '#333' : Color.White)
    .borderRadius(12)
    .margin({ bottom: 8 })
  }
}

5.3 PersistentStorage 工作原理

应用首次启动
  |
  v
PersistentStorage.persistProp('theme', 'light')  -- 初始化默认值
  |
  v
AppStorage.setOrCreate('theme', 'light')         -- 自动写入AppStorage
  |
  v
页面通过@StorageLink('theme') 读取              -- UI显示 "light"
  |
  v
用户修改为 "dark"                                 -- AppStorage更新为"dark"
  |
  v
PersistentStorage 自动同步到磁盘                  -- 磁盘存储 "dark"
  |
  v
应用退出后重新启动
  |
  v
PersistentStorage 从磁盘读取 "dark"              -- 恢复到AppStorage
  |
  v
页面通过@StorageLink('theme') 读取              -- UI显示 "dark"(已恢复)

六、方案选型决策树

当你需要选择存储方案时,可以按照以下决策流程来判断:

需要存储数据
  |
  +-- 数据是否需要在应用重启后保留?
  |     |
  |     +-- 是 --> 使用 PersistentStorage(持久化到磁盘)
  |     |         适用:用户设置、主题偏好、免登录token
  |     |
  |     +-- 否 --> 数据需要跨页面共享吗?
  |               |
  |               +-- 是 --> 使用 AppStorage(应用级内存)
  |               |         适用:登录态、全局配置、实时数据
  |               |
  |               +-- 否 --> 使用 LocalStorage(页面级内存)
  |                         适用:弹窗状态、页面内筛选条件

更直观的判断方法

你的需求

推荐方案

用户关闭App再打开,设置还在

PersistentStorage

所有页面都要显示用户名

AppStorage

点击按钮打开弹窗,弹窗内选完条件后列表刷新

LocalStorage

全局深色模式切换,所有页面生效

PersistentStorage(持久化)+ AppStorage(运行时)

当前页面的排序方式和筛选条件

LocalStorage

登录token,下次打开App免登录

PersistentStorage


七、综合实战:用户设置页面

下面通过一个完整的实战案例,展示三种Storage的配合使用。

7.1 需求分析

  • 用户的主题偏好(深色/浅色)-- 需要持久化 --> PersistentStorage

  • 用户的登录信息(昵称、头像)-- 跨页面共享 --> AppStorage

  • 设置页的弹窗显隐状态 -- 页面私有 --> LocalStorage

7.2 完整代码

// EntryAbility.ets -- 应用入口初始化持久化数据
import { PersistentStorage } from '@kit.ArkUI';
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 在Ability onCreate中初始化持久化
    PersistentStorage.persistProp('userTheme', 'light');
    PersistentStorage.persistProp('fontSize', 16);
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content.');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
    });
  }
}
// pages/UserSettings.ets

// 本地存储:页面级状态
const settingsPageStorage = new LocalStorage({
  'showLogoutDialog': false,
  'showAboutDialog': false
});

@Entry(settingsPageStorage)
@Component
struct UserSettings {
  // PersistentStorage持久化的数据(跨应用重启保留)
  @StorageLink('userTheme') userTheme: string = 'light';
  @StorageLink('fontSize') fontSize: number = 16;

  // AppStorage的应用级数据(跨页面共享,不持久化)
  @StorageLink('isLoggedIn') isLoggedIn: boolean = false;
  @StorageLink('userName') userName: string = '游客';
  @StorageLink('userAvatar') userAvatar: string = '';

  // LocalStorage的页面级数据(仅本页面使用)
  @LocalStorageLink('showLogoutDialog') showLogoutDialog: boolean = false;
  @LocalStorageLink('showAboutDialog') showAboutDialog: boolean = false;

  build() {
    Column() {
      // 顶部标题
      Text('设置')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ top: 16, bottom: 20, left: 16 })

      Scroll() {
        Column({ space: 12 }) {
          // 用户信息卡片
          this.UserInfoCard()

          // 主题设置(持久化)
          this.ThemeSettings()

          // 字体设置(持久化)
          this.FontSettings()

          // 账号操作(页面级弹窗控制)
          this.AccountActions()

          // 关于(页面级弹窗控制)
          this.AboutSection()
        }
        .padding({ left: 16, right: 16, bottom: 40 })
      }
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.userTheme === 'dark' ? '#1A1A1A' : '#F5F5F5')
  }

  // 用户信息卡片 -- 使用AppStorage数据
  @Builder
  UserInfoCard() {
    Row() {
      // 头像
      Circle({ width: 60, height: 60 })
        .fill(this.userTheme === 'dark' ? '#555' : '#E6F7FF')
        .overlay(
          Text(this.isLoggedIn ? this.userName.charAt(0) : '?')
            .fontSize(24)
            .fontColor(this.userTheme === 'dark' ? '#FFF' : '#1890FF')
        )

      Column() {
        Text(this.isLoggedIn ? this.userName : '点击登录')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .fontColor(this.userTheme === 'dark' ? '#FFF' : '#333')

        if (this.isLoggedIn) {
          Text('查看个人主页 >')
            .fontSize(13)
            .fontColor('#999')
            .margin({ top: 4 })
        }
      }
      .alignItems(HorizontalAlign.Start)
      .margin({ left: 16 })

      Blank()
    }
    .width('100%')
    .padding(16)
    .backgroundColor(this.userTheme === 'dark' ? '#333' : Color.White)
    .borderRadius(12)
    .onClick(() => {
      if (!this.isLoggedIn) {
        // 模拟登录
        this.isLoggedIn = true;
        this.userName = '鸿蒙开发者';
      }
    })
  }

  // 主题设置 -- 使用PersistentStorage数据
  @Builder
  ThemeSettings() {
    Column() {
      Text('显示设置')
        .fontSize(14)
        .fontColor('#999')
        .margin({ bottom: 8 })

      // 深色模式
      Row() {
        Text('深色模式')
          .fontSize(16)
          .fontColor(this.userTheme === 'dark' ? '#FFF' : '#333')
        Blank()
        Toggle({ type: ToggleType.Switch, isOn: this.userTheme === 'dark' })
          .onChange((isOn: boolean) => {
            this.userTheme = isOn ? 'dark' : 'light';
          })
      }
      .width('100%')
      .padding(16)
      .backgroundColor(this.userTheme === 'dark' ? '#333' : Color.White)
      .borderRadius(12)
    }
  }

  // 字体设置 -- 使用PersistentStorage数据
  @Builder
  FontSettings() {
    Column() {
      Text('阅读设置')
        .fontSize(14)
        .fontColor('#999')
        .margin({ bottom: 8 })

      Row() {
        Text('字体大小')
          .fontSize(16)
          .fontColor(this.userTheme === 'dark' ? '#FFF' : '#333')
        Blank()
        Row({ space: 12 }) {
          Button('-')
            .width(36)
            .height(36)
            .fontSize(18)
            .onClick(() => {
              if (this.fontSize > 12) {
                this.fontSize -= 2;
              }
            })
          Text(`${this.fontSize}sp`)
            .fontSize(16)
            .fontColor(this.userTheme === 'dark' ? '#FFF' : '#333')
            .width(50)
            .textAlign(TextAlign.Center)
          Button('+')
            .width(36)
            .height(36)
            .fontSize(18)
            .onClick(() => {
              if (this.fontSize < 24) {
                this.fontSize += 2;
              }
            })
        }
      }
      .width('100%')
      .padding(16)
      .backgroundColor(this.userTheme === 'dark' ? '#333' : Color.White)
      .borderRadius(12)
    }
  }

  // 账号操作 -- 使用LocalStorage控制弹窗
  @Builder
  AccountActions() {
    Column() {
      Text('账号管理')
        .fontSize(14)
        .fontColor('#999')
        .margin({ bottom: 8 })

      if (this.isLoggedIn) {
        Row() {
          Text('退出登录')
            .fontSize(16)
            .fontColor('#FF4D4F')
        }
        .width('100%')
        .padding(16)
        .backgroundColor(this.userTheme === 'dark' ? '#333' : Color.White)
        .borderRadius(12)
        .justifyContent(FlexAlign.Center)
        .onClick(() => {
          this.showLogoutDialog = true;  // LocalStorage控制弹窗
        })
      }
    }

    // 退出登录确认弹窗
    if (this.showLogoutDialog) {
      this.ConfirmDialog('确认退出登录?', () => {
        this.isLoggedIn = false;
        this.userName = '游客';
        this.showLogoutDialog = false;
      }, () => {
        this.showLogoutDialog = false;
      })
    }
  }

  // 关于信息 -- 使用LocalStorage控制弹窗
  @Builder
  AboutSection() {
    Column() {
      Text('关于')
        .fontSize(14)
        .fontColor('#999')
        .margin({ bottom: 8 })

      Row() {
        Text('关于应用')
          .fontSize(16)
          .fontColor(this.userTheme === 'dark' ? '#FFF' : '#333')
        Blank()
        Text('v1.0.0')
          .fontSize(14)
          .fontColor('#999')
      }
      .width('100%')
      .padding(16)
      .backgroundColor(this.userTheme === 'dark' ? '#333' : Color.White)
      .borderRadius(12)
      .onClick(() => {
        this.showAboutDialog = true;  // LocalStorage控制弹窗
      })
    }

    if (this.showAboutDialog) {
      this.ConfirmDialog('鸿蒙NEXT示例应用 v1.0.0', () => {
        this.showAboutDialog = false;
      }, () => {
        this.showAboutDialog = false;
      })
    }
  }

  // 通用确认弹窗
  @Builder
  ConfirmDialog(message: string, onConfirm: () => void, onCancel: () => void) {
    Column() {
      Text(message)
        .fontSize(16)
        .fontColor(this.userTheme === 'dark' ? '#FFF' : '#333')
        .margin({ bottom: 20 })

      Row({ space: 12 }) {
        Button('取消')
          .fontSize(14)
          .backgroundColor('#F0F0F0')
          .fontColor('#666')
          .onClick(onCancel)

        Button('确认')
          .fontSize(14)
          .backgroundColor('#1890FF')
          .onClick(onConfirm)
      }
    }
    .width('80%')
    .padding(24)
    .backgroundColor(this.userTheme === 'dark' ? '#444' : Color.White)
    .borderRadius(16)
    .justifyContent(FlexAlign.Center)
  }
}

八、常见误区与最佳实践

8.1 常见误区

误区1:把所有状态都放AppStorage
// 错误:把页面私有的临时状态放入AppStorage
AppStorage.setOrCreate('isShowDialog', false);        // 弹窗显隐是页面级的
AppStorage.setOrCreate('selectedTabIndex', 0);         // Tab索引是页面级的
AppStorage.setOrCreate('inputText', '');               // 输入框内容是页面级的

// 正确:只有真正需要跨页面共享的数据才放AppStorage
AppStorage.setOrCreate('userName', '张三');             // 用户信息,多页面需要
AppStorage.setOrCreate('userTheme', 'light');           // 主题设置,全局生效
误区2:PersistentStorage存储大量数据
// 错误:把大量数据塞进PersistentStorage
PersistentStorage.persistProp('productList', JSON.stringify(largeArray));  // 不要这样

// 正确:PersistentStorage只适合存储少量配置数据
PersistentStorage.persistProp('appTheme', 'light');      // 简单值
PersistentStorage.persistProp('fontSize', 16);           // 简单值
PersistentStorage.persistProp('lastPage', 'home');       // 简单值
// 大量数据使用关系型数据库(RDB)或Preferences
误区3:混淆LocalStorage和AppStorage的作用域
// 错误:以为LocalStorage的数据能在其他页面访问
// PageA.ets
const storageA = new LocalStorage({ 'sharedData': 'value' });
@Entry(storageA)
@Component
struct PageA {
  @LocalStorageLink('sharedData') data: string = '';
  // ...
}

// PageB.ets -- 访问不到PageA的LocalStorage
// @LocalStorageLink('sharedData') data: string = '';  // 这里是独立的,不是PageA的数据

// 正确:跨页面共享数据使用AppStorage
// PageA.ets
@StorageLink('sharedData') data: string = '';

// PageB.ets
@StorageLink('sharedData') data: string = '';  // 可以访问同一份数据
误区4:PersistentStorage调用时机错误
// 错误:在页面组件内部调用persistProp
@Entry
@Component
struct MyPage {
  aboutToAppear() {
    // 不要在这里调用!时机太晚,可能导致数据不同步
    PersistentStorage.persistProp('theme', 'light');
  }
}

// 正确:在Ability的onCreate或页面构建之前调用
// EntryAbility.ets
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  PersistentStorage.persistProp('theme', 'light');
}

8.2 最佳实践

实践1:封装StorageKey常量类
// common/StorageKeys.ets

/**
 * AppStorage键名常量,避免硬编码字符串导致拼写错误
 */
export class StorageKeys {
  // 用户相关
  static readonly USER_NAME = 'userName';
  static readonly IS_LOGGED_IN = 'isLoggedIn';
  static readonly USER_TOKEN = 'userToken';

  // 全局配置
  static readonly APP_THEME = 'appTheme';
  static readonly LANGUAGE = 'language';
  static readonly FONT_SIZE = 'fontSize';

  // 首次启动
  static readonly IS_FIRST_LAUNCH = 'isFirstLaunch';
}
// 使用常量类,避免拼写错误
@StorageLink(StorageKeys.APP_THEME) appTheme: string = 'light';
@StorageProp(StorageKeys.USER_NAME) userName: string = '';
实践2:类型安全的Storage访问
// 封装类型安全的Storage工具类
class StorageHelper {
  // 安全获取AppStorage中的值
  static get<T>(key: string, defaultValue: T): T {
    const value = AppStorage.get<T>(key);
    return value !== undefined ? value : defaultValue;
  }

  // 安全设置AppStorage中的值
  static set<T>(key: string, value: T): void {
    AppStorage.setOrCreate<T>(key, value);
  }

  // 检查key是否存在
  static has(key: string): boolean {
    return AppStorage.has(key);
  }
}

// 使用示例
const theme = StorageHelper.get<string>('appTheme', 'light');
StorageHelper.set<boolean>('isLoggedIn', true);
实践3:LocalStorage的组件间传递
// 父组件创建LocalStorage并传递给子组件
@Entry
@Component
struct ParentPage {
  private pageStorage = new LocalStorage({
    'selectedId': 0,
    'searchText': ''
  });

  build() {
    Column() {
      // 方式1:通过构造函数传递LocalStorage给子组件
      ChildComponent({ storage: this.pageStorage })
    }
  }
}

@Component
struct ChildComponent {
  // 通过构造函数接收LocalStorage
  storage: LocalStorage = new LocalStorage();

  // 通过传入的LocalStorage进行数据绑定
  @LocalStorageLink('selectedId') selectedId: number = 0;

  build() {
    Text(`选中ID:${this.selectedId}`)
      .onClick(() => {
        this.selectedId++;
      })
  }
}

九、总结

本文要点回顾

知识点

核心内容

AppStorage

应用级内存存储,所有页面共享,进程退出数据清除

LocalStorage

页面级内存存储,当前页面及其子组件可用,页面销毁数据清除

PersistentStorage

AppStorage的持久化增强层,将指定key写入磁盘,应用重启数据恢复

选型决策

需持久化用PersistentStorage,跨页面用AppStorage,页面私有用LocalStorage

选型速查表

场景

推荐方案

用户主题/语言偏好

PersistentStorage

登录态/用户信息

AppStorage

免登录Token

PersistentStorage

全局配置参数

AppStorage

页面内弹窗显隐

LocalStorage

页面内筛选条件

LocalStorage

大量结构化数据

关系型数据库(RDB)

轻量配置键值对

Preferences


系列文章推荐


如果这篇文章对你有帮助,请点赞、收藏、关注支持!你的支持是我持续创作的动力!

有问题欢迎在评论区讨论,我会及时回复!


标签:AppStorage | LocalStorage | PersistentStorage | 鸿蒙存储 | 状态管理 | HarmonyOS NEXT | ArkUI | ArkTS | DevEco Studio | 持久化存储 | 全局状态

Logo

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

更多推荐