【OpenHarmony/HarmonyOs 】化学学习 App 收藏夹功能实践AppStorage 本地持久化与响应式刷新收藏夹是学习类 App 中非常重要的功能。用户遇到常用元素、难记方程式、重点知识点时都希望能一键保存后续快速复习。本文结合我的「化学视界」项目聊聊我是如何开发收藏功能的。⭐一、为什么收藏功能不能只做一个按钮刚开始做收藏时很容易以为只要在详情页放一个爱心按钮就够了。但真正开发后会发现收藏系统至少要解决几个问题用户能收藏哪些内容收藏后如何持久保存多个页面之间如何同步收藏状态收藏夹如何分类展示取消收藏后列表如何刷新点击收藏项如何回到详情页我的项目里收藏对象包括三类⚛️ 元素。⚗️ 方程式。 知识点。所以我先设计了统一的收藏数据结构而不是给每种内容单独写一套逻辑。二、收藏数据结构设计收藏项定义如下exporttypeFavoriteTypeelement|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.getnumber(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.typetypeitem.idid)) { 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(itemitem.typetypeitem.idid);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.allFavoritesgetAllFavorites();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 来说收藏夹不只是“保存喜欢的内容”更是用户复习路径的一部分。把收藏功能做扎实整个学习闭环才会更完整。
【OpenHarmony/HarmonyOs 】化学学习 App 收藏夹功能实践:AppStorage 本地持久化与响应式刷新
【OpenHarmony/HarmonyOs 】化学学习 App 收藏夹功能实践AppStorage 本地持久化与响应式刷新收藏夹是学习类 App 中非常重要的功能。用户遇到常用元素、难记方程式、重点知识点时都希望能一键保存后续快速复习。本文结合我的「化学视界」项目聊聊我是如何开发收藏功能的。⭐一、为什么收藏功能不能只做一个按钮刚开始做收藏时很容易以为只要在详情页放一个爱心按钮就够了。但真正开发后会发现收藏系统至少要解决几个问题用户能收藏哪些内容收藏后如何持久保存多个页面之间如何同步收藏状态收藏夹如何分类展示取消收藏后列表如何刷新点击收藏项如何回到详情页我的项目里收藏对象包括三类⚛️ 元素。⚗️ 方程式。 知识点。所以我先设计了统一的收藏数据结构而不是给每种内容单独写一套逻辑。二、收藏数据结构设计收藏项定义如下exporttypeFavoriteTypeelement|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.getnumber(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.typetypeitem.idid)) { 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(itemitem.typetypeitem.idid);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.allFavoritesgetAllFavorites();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 来说收藏夹不只是“保存喜欢的内容”更是用户复习路径的一部分。把收藏功能做扎实整个学习闭环才会更完整。