AppStorage和LocalStorage有什么区别?鸿蒙全局状态管理方案选型指南

📖 鸿蒙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 | 持久化存储 | 全局状态

相关推荐
求学中--4 小时前
鸿蒙状态管理一文通:@State/@Prop/@Link/@Provide四大装饰器,15分钟彻底搞懂
华为·harmonyos
阿钱真强道5 小时前
19 小凌派 rk2206 鸿蒙 LiteOS-M 任务详解
华为·鸿蒙·任务·liteos·详解·rk2206·小凌派
阿钱真强道5 小时前
18 小凌派 rk2206 鸿蒙 liteos 如何通过修改配置文件,编译不通的案例
华为·鸿蒙·编译·案例·liteos·rk2206
nashane5 小时前
HarmonyOS 6学习:HWAsan监测开启后应用崩溃的终极解决方案
学习·华为·harmonyos·harmonyos 5
Exploring5 小时前
实测 Vibe Coding:快速开发 HarmonyOS 玩 Android 客户端
harmonyos
UnicornDev5 小时前
【Flutter x HarmonyOS 6】魔方计时APP——记录页面的UI设计
flutter·ui·华为·harmonyos·鸿蒙
Swift社区6 小时前
鸿蒙 PC + 手机 + 平板:一次真正的多端应用实战
智能手机·电脑·harmonyos
纤纡.7 小时前
HarmonyOS 鸿蒙 ArkTS 实战:从零开发生肖集卡抽奖小程序
华为·小程序·harmonyos·deveco studio
枫叶丹47 小时前
【HarmonyOS 6.0】Data Augmentation Kit端侧问答模型:本地化智能问答的技术演进
开发语言·华为·harmonyos