HarmonyOS APP<<古今职鉴定>>开源教程第10篇:全局状态与持久化存储
本篇深入学习 AppStorage 和 Preferences,实现收藏功能与错题本
·
本篇深入学习 AppStorage 和 Preferences,实现收藏功能与错题本
图:古今职鉴开源教程封面。本篇围绕「全局状态与持久化存储」展开。
学习目标
完成本篇后,你将能够:
- ✅ 深入理解 AppStorage 的使用
- ✅ 使用 Preferences 实现本地持久化
- ✅ 封装 StorageManager 工具类
- ✅ 实现收藏功能
预计学习时间
约 90 分钟
---
实战一:AppStorage 全局状态
第一步:理解 AppStorage
AppStorage 是应用级别的状态存储:
- 所有页面共享
- 应用退出后数据丢失
- 不会自动持久化
第二步:AppStorage 基本操作
// 设置或创建(推荐用于初始化)
AppStorage.setOrCreate<number>('count', 0);
// 设置值(必须已存在)
AppStorage.set<number>('count', 10);
// 获取值
const count = AppStorage.get<number>('count');
// 检查是否存在
if (AppStorage.has('count')) {
// 存在
}
// 删除
AppStorage.delete('count');
第三步:在组件中使用
@Entry
@Component
struct Lesson10Page {
// 从 AppStorage 读取(单向)
@StorageProp('globalCount') count: number = 0;
aboutToAppear() {
// 初始化全局状态
AppStorage.setOrCreate('globalCount', 0);
}
build() {
Column({ space: 20 }) {
Text(`全局计数: ${this.count}`)
.fontSize(24)
Button('增加')
.onClick(() => {
// 修改 AppStorage
const current = AppStorage.get<number>('globalCount') || 0;
AppStorage.set('globalCount', current + 1);
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#f8f6f5')
}
}
第四步:@StorageProp vs @StorageLink
// 单向绑定:只读取,不写回
@StorageProp('key') value: Type = defaultValue;
// 双向绑定:修改会同步到 AppStorage
@StorageLink('key') value: Type = defaultValue;
| 特性 | @StorageProp | @StorageLink |
|---|---|---|
| 数据流向 | 单向(只读) | 双向 |
| 组件修改 | 不同步 | 同步到 AppStorage |
| 适用场景 | 只需显示 | 需要修改 |
第五步:运行验证
hvigorw assembleHap --no-daemon
---
实战二:Preferences 本地持久化
第一步:理解 Preferences
Preferences 用于存储轻量级数据到本地文件:
- 数据持久化,重启后保留
- 适合存储配置、设置
- 不适合大量数据(建议 < 1MB)
第二步:获取 Preferences 实例
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct Lesson10Page {
private dataPreferences: preferences.Preferences | null = null;
async aboutToAppear() {
const context = getContext(this) as common.UIAbilityContext;
// 获取 Preferences 实例
this.dataPreferences = await preferences.getPreferences(context, 'myStore');
}
}
第三步:存储和读取数据
// 存储数据
async saveData() {
if (this.dataPreferences) {
await this.dataPreferences.put('username', '张三');
await this.dataPreferences.put('age', 25);
// 重要:flush 持久化到磁盘
await this.dataPreferences.flush();
}
}
// 读取数据
async loadData() {
if (this.dataPreferences) {
// 第二个参数是默认值
const username = await this.dataPreferences.get('username', '');
const age = await this.dataPreferences.get('age', 0);
}
}
// 删除数据
async deleteData() {
if (this.dataPreferences) {
await this.dataPreferences.delete('username');
await this.dataPreferences.flush();
}
}
第四步:存储复杂数据
Preferences 只支持基本类型,复杂数据需要 JSON 序列化:
interface FavoriteItem {
id: number;
name: string;
}
// 存储数组
async saveFavorites(favorites: FavoriteItem[]) {
if (this.dataPreferences) {
const jsonStr = JSON.stringify(favorites);
await this.dataPreferences.put('favorites', jsonStr);
await this.dataPreferences.flush();
}
}
// 读取数组
async loadFavorites(): Promise<FavoriteItem[]> {
if (this.dataPreferences) {
const jsonStr = await this.dataPreferences.get('favorites', '[]') as string;
return JSON.parse(jsonStr) as FavoriteItem[];
}
return [];
}
第五步:运行验证
hvigorw assembleHap --no-daemon
---
实战三:实现收藏功能
第一步:定义数据接口
interface PositionItem {
id: number;
name: string;
dynasty: string;
description: string;
}
interface FavoritePosition {
id: number;
name: string;
dynasty: string;
addTime: number;
}
第二步:创建收藏页面
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct Lesson10Page {
private dataPreferences: preferences.Preferences | null = null;
@State favorites: FavoritePosition[] = [];
@State currentTab: number = 0;
private positions: PositionItem[] = [
{ id: 1, name: '丞相', dynasty: '秦', description: '百官之长' },
{ id: 2, name: '太尉', dynasty: '秦', description: '掌管军事' },
{ id: 3, name: '御史大夫', dynasty: '秦', description: '监察百官' },
{ id: 4, name: '大司马', dynasty: '汉', description: '最高军事长官' },
{ id: 5, name: '尚书令', dynasty: '唐', description: '尚书省长官' }
];
async aboutToAppear() {
const context = getContext(this) as common.UIAbilityContext;
this.dataPreferences = await preferences.getPreferences(context, 'lesson10');
await this.loadFavorites();
}
async loadFavorites() {
if (this.dataPreferences) {
const jsonStr = await this.dataPreferences.get('favorites', '[]') as string;
this.favorites = JSON.parse(jsonStr) as FavoritePosition[];
}
}
async saveFavorites() {
if (this.dataPreferences) {
await this.dataPreferences.put('favorites', JSON.stringify(this.favorites));
await this.dataPreferences.flush();
}
}
isFavorite(id: number): boolean {
return this.favorites.some(f => f.id === id);
}
async toggleFavorite(item: PositionItem) {
const index = this.favorites.findIndex(f => f.id === item.id);
if (index >= 0) {
this.favorites.splice(index, 1);
} else {
this.favorites.push({
id: item.id,
name: item.name,
dynasty: item.dynasty,
addTime: Date.now()
});
}
await this.saveFavorites();
}
build() {
Column() {
// 头部
Row() {
Text('收藏功能演示')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
// Tab 切换
Row() {
this.TabButton('官职列表', 0)
this.TabButton(`我的收藏 (${this.favorites.length})`, 1)
}
.width('100%')
.padding(16)
// 内容
if (this.currentTab === 0) {
this.PositionList()
} else {
this.FavoriteList()
}
}
.width('100%')
.height('100%')
.backgroundColor('#f8f6f5')
}
@Builder
TabButton(title: string, index: number) {
Text(title)
.fontSize(14)
.fontColor(this.currentTab === index ? Color.White : '#1e293b')
.backgroundColor(this.currentTab === index ? '#c41e3a' : '#f0f0f0')
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(20)
.margin({ right: 12 })
.onClick(() => {
this.currentTab = index;
})
}
@Builder
PositionList() {
List() {
ForEach(this.positions, (item: PositionItem) => {
ListItem() {
Row() {
Column() {
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
Text(`${item.dynasty} · ${item.description}`)
.fontSize(13)
.fontColor('#64748b')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Image(this.isFavorite(item.id) ?
$r('app.media.ic_favorite_filled') :
$r('app.media.ic_favorite'))
.width(24)
.height(24)
.fillColor(this.isFavorite(item.id) ? '#c41e3a' : '#cccccc')
.onClick(() => {
this.toggleFavorite(item);
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.margin({ bottom: 12 })
}
}, (item: PositionItem) => item.id.toString())
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16 })
}
@Builder
FavoriteList() {
if (this.favorites.length === 0) {
Column() {
Text('暂无收藏')
.fontSize(16)
.fontColor('#64748b')
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
List() {
ForEach(this.favorites, (item: FavoritePosition) => {
ListItem() {
Row() {
Column() {
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
Text(`${item.dynasty}`)
.fontSize(13)
.fontColor('#64748b')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Image($r('app.media.ic_close'))
.width(20)
.height(20)
.fillColor('#cccccc')
.onClick(() => {
this.removeFavorite(item.id);
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.margin({ bottom: 12 })
}
}, (item: FavoritePosition) => item.id.toString())
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16 })
}
}
async removeFavorite(id: number) {
const index = this.favorites.findIndex(f => f.id === id);
if (index >= 0) {
this.favorites.splice(index, 1);
await this.saveFavorites();
}
}
}
第三步:运行验证
hvigorw assembleHap --no-daemon
预期效果:
- 点击心形图标可以收藏/取消收藏
- 切换到"我的收藏"Tab 查看收藏列表
- 重启应用后收藏数据保留
---
实战四:封装 StorageManager 工具类
第一步:创建工具类
// StorageManager.ets
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
export class StorageManager {
private static instance: StorageManager | null = null;
private dataPreferences: preferences.Preferences | null = null;
private constructor() {}
static getInstance(): StorageManager {
if (!StorageManager.instance) {
StorageManager.instance = new StorageManager();
}
return StorageManager.instance;
}
async init(context: common.UIAbilityContext): Promise<void> {
this.dataPreferences = await preferences.getPreferences(context, 'app_data');
}
async setString(key: string, value: string): Promise<void> {
if (!this.dataPreferences) return;
await this.dataPreferences.put(key, value);
await this.dataPreferences.flush();
}
async getString(key: string, defaultValue: string = ''): Promise<string> {
if (!this.dataPreferences) return defaultValue;
return await this.dataPreferences.get(key, defaultValue) as string;
}
async setObject<T>(key: string, value: T): Promise<void> {
if (!this.dataPreferences) return;
await this.dataPreferences.put(key, JSON.stringify(value));
await this.dataPreferences.flush();
}
async getObject<T>(key: string, defaultValue: T): Promise<T> {
if (!this.dataPreferences) return defaultValue;
const jsonStr = await this.dataPreferences.get(key, '') as string;
if (!jsonStr) return defaultValue;
try {
return JSON.parse(jsonStr) as T;
} catch {
return defaultValue;
}
}
async remove(key: string): Promise<void> {
if (!this.dataPreferences) return;
await this.dataPreferences.delete(key);
await this.dataPreferences.flush();
}
}
第二步:使用工具类
// 在 EntryAbility 中初始化
async onCreate() {
await StorageManager.getInstance().init(this.context);
}
// 在组件中使用
async aboutToAppear() {
this.favorites = await StorageManager.getInstance()
.getObject<FavoritePosition[]>('favorites', []);
}
async saveFavorites() {
await StorageManager.getInstance()
.setObject('favorites', this.favorites);
}
第三步:工具类的优势
- 单例模式,全局共享
- 类型安全的存取方法
- 简化异步操作
- 统一管理存储逻辑
---
完整代码
// 文件路径:products/jiaocheng/src/main/ets/lesson10/Lesson10Page.ets
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
interface PositionItem {
id: number;
name: string;
dynasty: string;
description: string;
}
interface FavoritePosition {
id: number;
name: string;
dynasty: string;
addTime: number;
}
@Entry
@Component
struct Lesson10Page {
private dataPreferences: preferences.Preferences | null = null;
@State favorites: FavoritePosition[] = [];
@State currentTab: number = 0;
private positions: PositionItem[] = [
{ id: 1, name: '丞相', dynasty: '秦', description: '百官之长' },
{ id: 2, name: '太尉', dynasty: '秦', description: '掌管军事' },
{ id: 3, name: '御史大夫', dynasty: '秦', description: '监察百官' },
{ id: 4, name: '大司马', dynasty: '汉', description: '最高军事长官' },
{ id: 5, name: '尚书令', dynasty: '唐', description: '尚书省长官' }
];
async aboutToAppear() {
const context = getContext(this) as common.UIAbilityContext;
this.dataPreferences = await preferences.getPreferences(context, 'lesson10');
await this.loadFavorites();
}
async loadFavorites() {
if (this.dataPreferences) {
const jsonStr = await this.dataPreferences.get('favorites', '[]') as string;
this.favorites = JSON.parse(jsonStr) as FavoritePosition[];
}
}
async saveFavorites() {
if (this.dataPreferences) {
await this.dataPreferences.put('favorites', JSON.stringify(this.favorites));
await this.dataPreferences.flush();
}
}
isFavorite(id: number): boolean {
return this.favorites.some(f => f.id === id);
}
async toggleFavorite(item: PositionItem) {
const index = this.favorites.findIndex(f => f.id === item.id);
if (index >= 0) {
this.favorites.splice(index, 1);
} else {
this.favorites.push({
id: item.id,
name: item.name,
dynasty: item.dynasty,
addTime: Date.now()
});
}
await this.saveFavorites();
}
async removeFavorite(id: number) {
const index = this.favorites.findIndex(f => f.id === id);
if (index >= 0) {
this.favorites.splice(index, 1);
await this.saveFavorites();
}
}
build() {
Column() {
Row() {
Text('收藏功能演示')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
Row() {
this.TabButton('官职列表', 0)
this.TabButton(`我的收藏 (${this.favorites.length})`, 1)
}
.width('100%')
.padding(16)
if (this.currentTab === 0) {
this.PositionList()
} else {
this.FavoriteList()
}
}
.width('100%')
.height('100%')
.backgroundColor('#f8f6f5')
}
@Builder
TabButton(title: string, index: number) {
Text(title)
.fontSize(14)
.fontColor(this.currentTab === index ? Color.White : '#1e293b')
.backgroundColor(this.currentTab === index ? '#c41e3a' : '#f0f0f0')
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(20)
.margin({ right: 12 })
.onClick(() => { this.currentTab = index; })
}
@Builder
PositionList() {
List() {
ForEach(this.positions, (item: PositionItem) => {
ListItem() {
Row() {
Column() {
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
Text(`${item.dynasty} · ${item.description}`)
.fontSize(13)
.fontColor('#64748b')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Image(this.isFavorite(item.id) ?
$r('app.media.ic_favorite_filled') :
$r('app.media.ic_favorite'))
.width(24)
.height(24)
.fillColor(this.isFavorite(item.id) ? '#c41e3a' : '#cccccc')
.onClick(() => { this.toggleFavorite(item); })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.margin({ bottom: 12 })
}
}, (item: PositionItem) => item.id.toString())
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16 })
}
@Builder
FavoriteList() {
if (this.favorites.length === 0) {
Column() {
Text('暂无收藏')
.fontSize(16)
.fontColor('#64748b')
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
List() {
ForEach(this.favorites, (item: FavoritePosition) => {
ListItem() {
Row() {
Column() {
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
Text(item.dynasty)
.fontSize(13)
.fontColor('#64748b')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Image($r('app.media.ic_close'))
.width(20)
.height(20)
.fillColor('#cccccc')
.onClick(() => { this.removeFavorite(item.id); })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.margin({ bottom: 12 })
}
}, (item: FavoritePosition) => item.id.toString())
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16 })
}
}
}
@Builder
export function Lesson10PageBuilder() {
Lesson10Page()
}
---
本课小结
核心知识点
| 知识点 | 说明 |
|---|---|
| AppStorage | 应用级全局状态,不持久化 |
| @StorageProp | 单向绑定 AppStorage |
| @StorageLink | 双向绑定 AppStorage |
| Preferences | 本地持久化存储 |
| flush() | 持久化到磁盘(必须调用) |
| JSON 序列化 | 存储复杂数据类型 |
存储方案选择
| 场景 | 推荐方案 |
|---|---|
| 跨页面共享状态 | AppStorage |
| 需要持久化的配置 | Preferences |
| 大量结构化数据 | 关系型数据库 |
---
课后练习
练习1:实现错题本功能
记录用户答错的题目,支持查看和删除。
练习2:添加收藏排序
支持按收藏时间或名称排序。
---
下一课预告
第11课我们将学习响应式布局,包括:
- 屏幕信息获取
- 断点设计与响应式策略
- 媒体查询
- 横竖屏适配
项目开源地址
更多推荐


所有评论(0)