Preferences 最近浏览重复怎么办:中式美食菜谱详情页怎么去重、排序和限制数量

发布时间:2026/7/16 2:48:54
Preferences 最近浏览重复怎么办:中式美食菜谱详情页怎么去重、排序和限制数量 先把问题说清楚中式美食这个页面的问题一开始不是那种一眼能看到的崩溃而是用着用着才露出来每次进详情都直接 push 一条记录同一道菜会重复出现时间越久列表越脏。。如果只是开发阶段点两下页面可能会觉得没什么但真的按用户习惯去搜菜、切分类、进详情、返回、再收藏问题就会变得很明显。我后来没有先怪框架也没有直接把页面重写一遍而是先把状态边界拆开看。这个问题真正麻烦的地方是页面上看到的是一个现象背后其实牵着数据、路由、列表复用和局部刷新几个点。只修其中一个短时间看起来能好换个操作顺序又会复现。这篇按中式美食的 菜谱详情页 来讲我是怎么复现、怎么定位、怎么改、最后怎么确认它真的稳定了。涉及的页面和状态项目说明页面中式美食 菜谱详情页主要技术HarmonyOS、ArkUI、ArkTS、Preferences关键状态viewedAt、recipeId、filterKey、sortMode目标页面状态清楚返回和刷新后不串、不丢、不乱这里最重要的是别把所有变化都当成“页面刷新”。有的变化只是某一项变了有的变化才是整个列表结果变了。分不清这两类后面一定会乱。一开始的写法为什么容易出问题我最早的处理比较粗页面变化了就重新算一遍列表详情回来也重新查一遍状态变了再把整个页面数据塞回去。这个写法省事但它把很多不同原因混成了一个动作。typeRecentViewedStoreState{keyword:stringcategory:stringsortMode:default|favorite|recentviewedAt:string|number|booleanrecipes:RecipeItem[]}如果只是 demo这样写能跑。但中式美食不是只展示一个静态数组。用户在页面里的动作是连续的先筛选再进详情再返回再切换分类。每一步都可能改变数据也可能只改变当前页面现场。functionreloadWholePage(nextState:PartialRecentViewedStoreState){this.state{...this.state,...nextState,recipes:buildRecipeList(nextState)}}这个写法的问题是太笼统。它不区分“状态局部变化”和“列表结果变化”所以后面排查时很难判断到底是哪一步把现场弄丢了。我先把变化分成三类后来我把页面变化分成三类这一步比直接写代码更重要。变化类型例子处理方式页面现场viewedAt、当前分类、关键词单独保存不随便清空单项变化收藏、评分、最近浏览时间只更新对应 recipeId结果重排搜索词变化、分类切换、排序变化重新计算可见列表这样拆完以后代码反而简单了。因为每个动作只负责自己的结果不再互相抢状态。状态要有明确入口我现在会给 菜谱详情页 单独留一个状态入口而不是让页面里到处改变量。这样做不是为了显得架构复杂而是为了以后出了问题能知道从哪里查。classRecentViewedStoreController{privatestate:RecentViewedStoreStatepatchScene(next:PartialRecentViewedStoreState):void{this.state{...this.state,...next}}current():RecentViewedStoreState{returnthis.state}}这段代码看着普通但它解决的是“谁能改状态”的问题。以前页面、卡片、详情返回都可能改同一份数据现在至少能把入口收住。具体修正怎么做修正方向就是一句话用 recipeId 先去重再更新时间戳最后按 viewedAt 倒序截断。。functionapplyStableChange(recipeId:string,patch:PartialRecipeItem){constoldItemthis.recipeMap.get(recipeId)if(!oldItem){return}constnextItem{...oldItem,...patch}this.recipeMap.set(recipeId,nextItem)this.dataSource.notifyRecipeChanged(recipeId,nextItem)}这里我没有让每个组件自己决定要不要刷新整页而是把更新交给统一入口。卡片只告诉外面“哪一道菜变了”外面再决定怎么改数据源。RecipeCard({recipe,onFavoriteChange:(recipeId:string){this.applyStableChange(recipeId,{favorite:!recipe.favorite})}})这个写法的好处是卡片不会偷偷带着旧对象去改状态。它只传稳定的 recipeId真正的数据修改在页面层或仓储层完成。什么时候不能恢复旧现场有一个点也要注意不是所有时候都应该恢复旧状态。比如用户主动切换分类页面回到新分类顶部是合理的但用户从详情页返回继续停在刚才的位置才合理。场景是否恢复现场原因从详情页返回恢复用户还在同一条浏览链路里修改搜索关键词不强行恢复列表结果已经变了只更新收藏状态恢复数据变了列表现场没变切换大分类看产品预期通常应该回到新分类开头这个判断写清楚以后问题会少很多。以前所有刷新都走同一套逻辑页面自然会出现“该恢复的时候没恢复不该恢复的时候又恢复”的情况。我怎么验证它真的修好了这类问题不能只测一次页面打开。我会按真实操作链路测步骤预期打开中式美食 菜谱详情页首屏数据正常搜索一道菜再向下滚动列表位置和筛选条件能保存进入详情后返回回到原来的筛选和位置收藏一项再返回列表只变当前菜谱不影响别的卡片切换分类再切回来状态仍然跟着 recipeId不跟着位置只要其中一步不稳定就不要急着发版。因为这类问题上线后会很影响用户感觉他不会知道你内部是状态没拆清楚他只会觉得页面“不记得刚才看哪了”。防回归我会留一个小检查修完以后我不会只靠手感判断。中式美食这种页面后面还会继续加功能今天修好的状态边界明天很可能被新的筛选、新的入口或者新的推荐模块打破。所以我会把最容易出问题的几个点写成一个很轻的检查函数开发时随手跑一下。functionassertSceneStable(before:RecentViewedStoreState,after:RecentViewedStoreState){if(before.keywordafter.keywordbefore.categoryafter.category){if(before.viewedAt!after.viewedAt){console.info(现场发生变化需要确认是不是用户主动操作)}}constbeforeIdsbefore.recipes.map(itemitem.recipeId).join(,)constafterIdsafter.recipes.map(itemitem.recipeId).join(,)if(beforeIds!afterIdsbefore.sortModeafter.sortMode){console.info(列表顺序变化了检查是不是误触发了整表重排)}}这段代码不是为了上线运行而是为了提醒自己状态变化要有原因。尤其是 菜谱详情页 这种入口很多 bug 不是一处代码写错而是几个“看起来没关系”的动作叠在一起。提前把检查点列出来后面改需求时就不容易把旧问题带回来。我还会在提交前看两类数据。第一类是页面现场比如viewedAt有没有被无意义清空第二类是业务身份比如 recipeId 有没有在列表重排后还能对上同一道菜。只要这两类数据稳定页面表现通常就不会差太多。以后怎么避免我现在写中式美食这类页面会先问三个问题。第一这个状态到底属于谁属于页面现场就不要塞进单个卡片属于某一道菜就必须跟 recipeId 走属于筛选结果就不要跟详情返回混在一起。第二这次变化是不是必须重建整个列表如果只是收藏、浏览、评分这种小变化就不要整表重建。整表重建虽然省事但后面滚动、复用、状态都会受影响。第三验证链路是不是接近真实用户操作只点一次按钮不够要把搜索、筛选、详情返回、收藏、再切换这些动作连起来测。这个问题最后给我的提醒是Preferences 相关问题很多时候不是 API 不好用而是状态边界没拆清楚。先把“谁负责保存、谁负责更新、什么时候重排”讲清楚再写代码页面就会稳很多。