AppStorage和LocalStorage有什么区别?鸿蒙全局状态管理方案选型指南
AppStorage 是鸿蒙提供的应用级状态存储,它在整个应用进程的生命周期内维护一个键值对存储。所有页面和组件都可以通过 @StorageProp 或 @StorageLink 装饰器与之建立双向或单向的数据绑定。
📖 鸿蒙NEXT开发实战系列 | 第18篇 | 进阶篇 🎯 适合人群:有鸿蒙基础状态管理经验的开发者 ⏰ 阅读时间:约12分钟 | 💻 开发环境:DevEco Studio 5.0+
上一篇:DevEco Studio必备工具清单 | 下一篇:待更新
目录
一、引言:三个Storage到底该怎么选?
在鸿蒙NEXT开发中,当你的应用规模逐渐增大,组件之间的状态共享需求会越来越多。这时你会发现,除了 @State、@Link、@Provide/@Consume 这些组件级状态装饰器之外,还有三个以 "Storage" 命名的方案:
-
AppStorage -- 应用级状态存储
-
LocalStorage -- 页面级状态存储
-
PersistentStorage -- 持久化存储
很多开发者在第一次接触时都会感到困惑:它们看起来都能存数据,到底有什么区别?什么时候该用哪个?
本文将通过对比表格 + 选型决策树 + 完整实战代码,帮你彻底理清这三种存储方案的定位和用法。
二、三种Storage全景对比
先上一张对比表,帮你建立全局认知:
|
对比维度 |
AppStorage |
LocalStorage |
PersistentStorage |
|---|---|---|---|
|
作用域 |
整个应用进程 |
单个页面/组件树 |
整个应用进程 |
|
生命周期 |
应用存活期间,退出即清除 |
页面存活期间,页面销毁即清除 |
永久保存,重启应用后仍然存在 |
|
数据持久化 |
否,内存级 |
否,内存级 |
是,写入磁盘 |
|
典型用途 |
用户登录态、全局主题、语言设置 |
弹窗状态、页面内部配置 |
用户设置偏好、历史记录 |
|
UI联动装饰器 |
|
|
配合 |
|
数据共享范围 |
任意页面和组件 |
当前页面及其子组件 |
任意页面和组件 |
|
API来源 |
|
构造函数传入 |
|
一句话总结:
-
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 |
不可以,仅限当前页面 |
|
初始化方式 |
|
|
|
注入方式 |
自动全局可访问 |
|
|
典型场景 |
用户信息、主题、语言 |
页面内筛选条件、弹窗状态 |
五、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 |
系列文章推荐
-
第18篇:AppStorage和LocalStorage选型指南(当前)
如果这篇文章对你有帮助,请点赞、收藏、关注支持!你的支持是我持续创作的动力!
有问题欢迎在评论区讨论,我会及时回复!
标签:AppStorage | LocalStorage | PersistentStorage | 鸿蒙存储 | 状态管理 | HarmonyOS NEXT | ArkUI | ArkTS | DevEco Studio | 持久化存储 | 全局状态
更多推荐



所有评论(0)