【OpenHarmony/HarmonyOs 】化学学习 App 收藏夹功能实践:AppStorage 本地持久化与响应式刷新
收藏夹是学习类 App 中非常重要的功能。用户遇到常用元素、难记方程式、重点知识点时,都希望能一键保存,后续快速复习。本文结合我的「化学视界」项目,聊聊我是如何开发收藏功能的。⭐
一、为什么收藏功能不能只做一个按钮?
刚开始做收藏时,很容易以为只要在详情页放一个爱心按钮就够了。但真正开发后会发现,收藏系统至少要解决几个问题:
- 用户能收藏哪些内容?
- 收藏后如何持久保存?
- 多个页面之间如何同步收藏状态?
- 收藏夹如何分类展示?
- 取消收藏后列表如何刷新?
- 点击收藏项如何回到详情页?
我的项目里,收藏对象包括三类:
- ⚛️ 元素。
- ⚗️ 方程式。
- 📚 知识点。
所以我先设计了统一的收藏数据结构,而不是给每种内容单独写一套逻辑。
二、收藏数据结构设计
收藏项定义如下:
exporttypeFavoriteType='element'|'equation'|'knowledge';exportinterfaceFavoriteItem {id:string;type:FavoriteType;title:string;subtitle:string;dateAdded:string; }这里的几个字段分别解决不同问题:
id:定位原始内容。type:区分元素、方程式、知识点。title:收藏夹列表主标题。subtitle:辅助说明,比如元素符号、方程式内容、知识标签。dateAdded:记录收藏时间。
我开发时的一个经验是:收藏夹列表不要只存 id。只存 id 虽然最干净,但列表展示时还要反查原始数据。适当冗余title和subtitle,可以让收藏夹展示更直接。
三、使用 AppStorage 保存收藏数据
收藏数据写入本地状态:
functionwriteFavorites(items: FavoriteItem[]): void {AppStorage.setOrCreate(STORAGE_KEY, JSON.stringify(items)); const marker = (AppStorage.get<number>(STORAGE_VERSION_KEY) ??0) +1;AppStorage.setOrCreate(STORAGE_VERSION_KEY,marker); }这里有一个关键点:我除了保存收藏数组,还额外维护了一个刷新标记。
原因是收藏可能发生在多个页面:
- 元素详情页点击收藏。
- 方程式详情页点击收藏。
- 知识详情页点击收藏。
- 收藏夹里取消收藏。
如果没有统一刷新机制,某个页面的收藏状态很容易滞后。
四、添加收藏:先去重再写入
添加收藏时,我先读取已有收藏,再判断是否已经收藏过:
exportfunctionaddFavorite(type: FavoriteType,id:string,title:string): boolean { const items = readFavorites();if(items.some(item => item.type===type&&item.id===id)) { returnfalse; } items.push({ id,type, title, subtitle: getSubtitle(type,id), dateAdded: formatDate(newDate()) }); writeFavorites(items); returntrue; }这里我用type + id判断唯一性,因为元素、方程式、知识点可能都有数字 id,单独用 id 不够安全。
五、切换收藏状态:详情页最常用
详情页里一般不需要关心“添加”还是“删除”,只要调用切换函数:
exportfunctiontoggleFavorite(type: FavoriteType,id:string,title:string): boolean { const items = readFavorites(); const idx = items.findIndex(item=>item.type===type&&item.id===id);if(idx >=0) { removeFavorite(type,id); returnfalse; }else{ addFavorite(type,id,title); returntrue; } }元素详情页中就是这样使用的:
handleFavoriteClick() {if(this.elementLocal) { constnewState= toggleFavorite('element',String(this.elementLocal.number),this.elementLocal.name ); animateTo({ duration:300, curve:Curve.EaseOut }, () => {this.isFavorite =newState; }); } }这样详情页逻辑就很清楚:点击按钮 → 切换状态 → 更新 UI。
六、收藏夹页面:用 StorageLink 自动响应变化
收藏夹页面使用刷新标记监听收藏变化:
@StorageLink(FAVORITES_VERSION_KEY)@Watch('onVersionChange')version: number =0;当收藏数据变化时,页面重新加载收藏列表:
onVersionChange() {this.watchVersionChange(); }watchVersionChange() {this.allFavorites=getAllFavorites();this.updateFilteredFavorites(); }我当时这样设计,是为了避免在每个页面之间手动传递事件。收藏数据变化后,只要刷新标记变化,收藏夹自然更新。
七、收藏夹分类展示
收藏夹顶部有四个分类:
- 全部。
- 元素。
- 方程式。
- 知识。
过滤逻辑如下:
updateFilteredFavorites(): void {if(this.selectedTab ===0) {this.filteredFavorites =this.allFavorites; }elseif(this.selectedTab ===1) {this.filteredFavorites =this.allFavorites.filter( (f: FavoriteItem) => f.type ==='element'); }elseif(this.selectedTab ===2) {this.filteredFavorites =this.allFavorites.filter( (f: FavoriteItem) => f.type ==='equation'); }else{this.filteredFavorites =this.allFavorites.filter( (f: FavoriteItem) => f.type ==='knowledge'); } }这部分我没有做复杂抽象,因为分类只有几种,直接写清楚反而更容易维护。
八、从收藏夹跳回详情页
收藏夹不能只是一个静态列表,点击收藏项后应该回到对应详情页:
navigateToDetail(item:FavoriteItem) {if(item.type=== 'element') {router.pushUrl({url: 'pages/periodic/ElementDetail',params: {elementNumber:Number(item.id) }}); }elseif(item.type=== 'equation') {router.pushUrl({url: 'pages/equations/EquationDetail',params: {equationId:Number(item.id) }}); }elseif(item.type=== 'knowledge') {router.pushUrl({url: 'pages/knowledge/KnowledgeDetail',params: {knowledgeId:Number(item.id) }}); } }这也是我前面统一收藏结构的好处:一个type就能决定跳转目标。
九、开发中遇到的关键点
1. 收藏状态要跨页面一致
如果只在当前页面维护isFavorite,收藏夹不会自动刷新。所以我用了刷新标记做全局同步。
2. 收藏项要有展示字段
只保存 id 会让收藏夹很难展示,所以我保存了title和subtitle。
3. 删除收藏要走统一方法
无论是在详情页取消收藏,还是收藏夹里删除,都调用同一套工具函数,避免数据不一致。
十、总结
收藏功能看起来简单,但要做完整,需要处理数据结构、持久化、状态同步、分类展示和详情跳转。
我的实现思路是:
- ⭐ 用统一结构保存不同类型内容。
- 💾 用本地状态保存收藏列表。
- 🔄 用刷新标记驱动页面自动更新。
- 🧭 用
type + id实现详情跳转。 - 🎨 用分类和图标提升可读性。
对于学习类 App 来说,收藏夹不只是“保存喜欢的内容”,更是用户复习路径的一部分。把收藏功能做扎实,整个学习闭环才会更完整。