HarmonyOS7 AppStorage 和 PersistentStorage 到底该选谁?全局状态别再用乱
文章目录
前言
做鸿蒙开发,绕不开一个问题:用户登录了,首页要显示昵称,"我的"页面要显示头像,设置页要展示会员等级。多个页面共享同一份数据,怎么搞?
@State 和 @Prop 搞不定跨组件通信,这时候就该 AppStorage 出场了。但如果用户杀了应用再打开,你还想保留这些数据呢?那得加上 PersistentStorage。
今天把这两个东西掰清楚了讲,再给个实际项目中的搭配方案。

AppStorage:内存级的全局仓库
AppStorage 是个单例的键值对存储,整个应用生命周期内有效。你往里塞个值,任何组件都能读出来。
核心 API 很简单:
// 写入
AppStorage.setOrCreate<string>('userName', '张三')
AppStorage.setOrCreate<number>('userId', 10086)
// 读取
let name = AppStorage.get<string>('userName') // '张三'
// 删除
AppStorage.delete('userName')
// 检查是否存在
AppStorage.has('userName') // false
在组件里用起来更顺手,用 @StorageProp 和 @StorageLink:
@Component
struct UserProfile {
// 只读——AppStorage 变了,UI 会刷新,但改这个变量不会写回
@StorageProp('userName') userName: string = ''
// 双向——改了这个变量,AppStorage 里的值也变
@StorageLink('themeColor') themeColor: string = '#3498db'
build() {
Column() {
Text(`欢迎回来,${this.userName}`)
.fontSize(18)
Button('切换主题色')
.onClick(() => {
this.themeColor = this.themeColor === '#3498db' ? '#e74c3c' : '#3498db'
})
.backgroundColor(this.themeColor)
}
}
}

@StorageProp 是单向的,组件读 AppStorage 的值,但改本地变量不会影响全局。@StorageLink 是双向的,改一头另一头跟着动。
AppStorage 的局限:它是纯内存的。应用被系统杀掉、用户手动退出,数据就没了。下次打开又是白纸一张。
PersistentStorage:能活过重启的存储
PersistentStorage 解决的就是持久化问题。它把数据写到磁盘上,应用重启后自动恢复。
用法也不复杂:
// 在应用启动时调用,通常在 UIAbility 的 onCreate 里
PersistentStorage.persistProp<string>('userName', '')
PersistentStorage.persistProp<string>('themeColor', '#3498db')
PersistentStorage.persistProp<string>('language', 'zh-CN')
persistProp 的意思是:如果磁盘上有这个 key 的值,就恢复到 AppStorage 里;如果没有,就用给的默认值。
之后在组件里照常用 @StorageProp 或 @StorageLink 读写,完全不用关心底层是内存还是磁盘。PersistentStorage 自动在后台同步。
两者配合的最佳实践
实际项目中,不是所有数据都需要持久化。我一般这么分:
| 数据类型 | 存储方式 | 例子 |
|---|---|---|
| 需要持久化的设置 | PersistentStorage + @StorageLink |
主题色、语言、字体大小 |
| 临时全局状态 | AppStorage + @StorageLink |
登录态、当前页面索引 |
| 敏感信息 | 不存这里 | Token 走安全存储 |
关键原则:PersistentStorage 负责"恢复现场",AppStorage 负责"运行时共享"。

实战:登录态 + 主题色 + 语言的全局管理
先看应用入口,在 UIAbility 里初始化持久化存储:
// entry/src/main/ets/entryability/EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility'
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 持久化这些需要跨重启保留的设置
PersistentStorage.persistProp<string>('themeColor', '#3498db')
PersistentStorage.persistProp<string>('language', 'zh-CN')
PersistentStorage.persistProp<number>('fontSize', 16)
PersistentStorage.persistProp<boolean>('isDarkMode', false)
// 登录态不需要持久化(安全考虑)
AppStorage.setOrCreate<boolean>('isLoggedIn', false)
AppStorage.setOrCreate<string>('userName', '')
}
}
注意:Token、密码这类敏感信息不要往 PersistentStorage 里塞。它底层用的是 Preferences,数据是明文存储的。敏感信息应该走 @ohos.security.keystore 或业务侧加密后存储。
然后搞一个设置页面:
@Component
struct SettingsPage {
@StorageLink('themeColor') themeColor: string = '#3498db'
@StorageLink('language') language: string = 'zh-CN'
@StorageLink('fontSize') fontSize: number = 16
@StorageLink('isDarkMode') isDarkMode: boolean = false
@StorageLink('isLoggedIn') isLoggedIn: boolean = false
@StorageLink('userName') userName: string = ''
build() {
Column({ space: 20 }) {
Text('设置')
.fontSize(22)
.fontWeight(FontWeight.Bold)
// 主题色选择
Row() {
Text('主题色')
.fontSize(this.fontSize)
Blank()
Row({ space: 12 }) {
Circle({ width: 30, height: 30 })
.fill('#3498db')
.onClick(() => { this.themeColor = '#3498db' })
Circle({ width: 30, height: 30 })
.fill('#e74c3c')
.onClick(() => { this.themeColor = '#e74c3c' })
Circle({ width: 30, height: 30 })
.fill('#2ecc71')
.onClick(() => { this.themeColor = '#2ecc71' })
}
}
.width('100%')
.padding(12)
// 语言选择
Row() {
Text('语言')
.fontSize(this.fontSize)
Blank()
Text(this.language === 'zh-CN' ? '中文' : 'English')
.fontSize(this.fontSize)
.onClick(() => {
this.language = this.language === 'zh-CN' ? 'en-US' : 'zh-CN'
})
}
.width('100%')
.padding(12)
// 字体大小
Row() {
Text('字体大小')
.fontSize(this.fontSize)
Blank()
Slider({
value: this.fontSize,
min: 12,
max: 24,
step: 1
})
.width(150)
.onChange((value: number) => {
this.fontSize = value
})
Text(`${this.fontSize}`)
.width(30)
}
.width('100%')
.padding(12)
// 暗色模式
Row() {
Text('暗色模式')
.fontSize(this.fontSize)
Blank()
Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
.onChange((isOn: boolean) => {
this.isDarkMode = isOn
})
}
.width('100%')
.padding(12)
// 模拟登录/登出
Button(this.isLoggedIn ? '退出登录' : '登录')
.backgroundColor(this.isLoggedIn ? '#999' : this.themeColor)
.onClick(() => {
if (this.isLoggedIn) {
this.isLoggedIn = false
this.userName = ''
} else {
this.isLoggedIn = true
this.userName = '张三'
}
})
}
.padding(20)
}
}
首页读取这些设置:
@Component
struct HomePage {
@StorageProp('userName') userName: string = ''
@StorageProp('isLoggedIn') isLoggedIn: boolean = false
@StorageProp('themeColor') themeColor: string = '#3498db'
@StorageProp('fontSize') fontSize: number = 16
build() {
Column() {
Text(this.isLoggedIn ? `Hi, ${this.userName}` : '请先登录')
.fontSize(this.fontSize + 4)
.fontColor(this.themeColor)
Text('这里是首页内容区域')
.fontSize(this.fontSize)
.margin({ top: 20 })
}
.width('100%')
.padding(20)
}
}
用户在设置页改了主题色,首页的标题颜色立刻跟着变。杀应用重启,主题色还是上次选的那个,但登录状态没了——因为登录态走的是 AppStorage,不持久化。
几个实用建议
不要在 PersistentStorage 里存大对象。它底层用的 Preferences 有大小限制,存个字符串、数字、布尔值没问题,塞个几百 KB 的 JSON 就不合适了。大数据走关系型数据库或文件。
初始化的时机要早。PersistentStorage.persistProp 要在任何组件读取之前调用,最靠谱的地方就是 UIAbility.onCreate。放在页面组件的 aboutToAppear 里可能会来不及。
命名要规范。key 都是全局的,不同模块之间容易撞名。我建议加前缀,比如 settings.themeColor、user.name。
最后聊两句
AppStorage 和 PersistentStorage 这对搭档,用起来其实挺简单的。核心就是分清楚哪些数据要持久化、哪些不用。
真正让人纠结的是"什么时候该用全局状态"。我的原则是:能不用就不用。如果数据只在一个组件树里流转,老老实实用 @State + @Prop + @Link 往下传。全局状态是个诱惑,用多了会让代码变得难追踪。等数据真正需要跨页面、跨模块共享了,再把 AppStorage 请出来。
更多推荐

所有评论(0)