当前位置: 首页 > news >正文

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

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

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


目录

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

  • 二、三种Storage全景对比

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

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

  • 五、PersistentStorage:持久化存储

  • 六、方案选型决策树

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

  • 八、常见误区与最佳实践

  • 九、总结


一、引言:三个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、用户昵称)

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

  • 全局语言设置

  • 全局配置参数

3.2 基础用法:@StorageLink 和 @StorageProp

// 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') } }

3.3 @StorageLink vs @StorageProp

对比项

@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


系列文章推荐

  • 第1篇:鸿蒙NEXT开发从零到一

  • 第2篇:ArkUI组件库完全指南

  • 第3篇:状态管理一文通

  • 第4篇:数据持久化与网络请求全攻略

  • 第5篇:性能优化实战指南

  • 第8篇:鸿蒙NEXT开发环境搭建全攻略

  • 第9篇:ArkTS语法速成

  • 第10篇:鸿蒙面试题TOP30

  • 第11篇:ArkUI组件库完全指南

  • 第12篇:鸿蒙布局终极指南

  • 第13篇:ArkUI高级布局技巧

  • 第14篇:ArkUI电商首页实战

  • 第15篇:DevEco Studio必备工具清单

  • 第18篇:AppStorage和LocalStorage选型指南(当前)


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

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


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

http://www.jsqmd.com/news/790281/

相关文章:

  • 067、连续轨迹运动:线性插值
  • 从Gazebo仿真到真机部署:一文搞懂MoveIt的ros_control控制器配置核心(以六轴机械臂为例)
  • 如何快速诊断Windows热键冲突:Hotkey Detective完整使用指南
  • 如何用嘎嘎降AI处理研究生毕业论文:硕士学位论文全流程降AI4.8元完整操作教程
  • 068、连续轨迹运动:圆弧插值
  • 最高年薪70w!大厂集体加码AI,新一轮就业风口正式开启
  • 从渔船到货轮:聊聊AIS Class A/B/SART设备怎么选,以及那些年我们踩过的安装坑
  • 2026年靠谱iOS加固服务哪家强?技术、效果、服务、成本四维对比
  • 《梦醒后只剩自己》的传播入口:醒来场景如何连接听众
  • 【仅限首批2000名开发者】:获取奇点大会AI原生CR沙箱环境访问权+5套企业级审查策略模板(含金融/车规/医疗三类合规预置包)
  • 【绝密级技术简报】:奇点大会安全工作组内部推演结论——AI原生框架将在2027Q2成为GDPR-AI、NIST AI RMF 2.0及中国《生成式AI服务安全基本要求》强制基线(附迁移路线图)
  • 给芯片做“体检”:聊聊VLSI测试那些事儿,从故障模型到BIST实战
  • 如何彻底掌控你的微信聊天数据:WeChatMsg完全解决方案
  • 3分钟学会百度网盘秒传技术:永久分享大文件的终极解决方案
  • 递归构建树形JSON结构的函数
  • 利用Taotoken多模型能力为AIGC应用提供不同风格的文本生成
  • 别再手动搭环境了!用这个开源工具5分钟在线调试Vue组件(支持Element-UI和iView)
  • 在OpenClaw中配置Taotoken作为大模型供应商的详细步骤
  • 如何永久重置IDM试用期:完整免费使用指南
  • AI原生MLOps落地失败率高达68%?(2026奇点大会闭门报告首度解密:模型漂移响应延迟>11.3秒即触发SLA熔断)
  • 从Python列表到Numpy数组:手把手教你数据科学入门必备的ndarray操作避坑指南
  • 069、连续轨迹运动:样条插值(B样条)
  • 如何验证降AI效果:降AI完成后AIGC检测验收完整操作流程免费教程
  • Meshroom完整指南:三步从照片到3D模型的魔法转换
  • 别再为驱动发愁了!实测Realtek RTL8156B-CG这款2.5G USB网卡的免驱体验到底有多香
  • 平衡车/四轴飞行器新手必看:用互补滤波搞定MPU6050姿态解算(附Arduino代码)
  • AI原生CI/CD的“最后一公里”破局:SITS2026如何用动态沙箱+意图验证双机制终结幻觉部署
  • 运城门窗推荐|本土合规型材门窗企业 山西铭发铝业实力综述 - 江湖评测
  • 终极指南:3分钟掌握B站成分检测器,让评论区用户画像一目了然
  • 新手教程使用curl命令在任意环境中测试Taotoken大模型接口