ArkUI 筛选弹窗关闭后状态没恢复怎么办:中式美食怎么区分临时选中和确认结果
筛选弹窗的问题不是弹窗本身而是状态边界。中式美食搜索页里用户打开筛选后可能只是试试看如果临时点击马上影响列表用户取消时页面已经被改过了这种体验很别扭。我的处理原则很直接页面先把用户看到的问题挡住数据层再把真正的边界兜住最后用可重复的操作去验收。这样写出来的逻辑不一定最炫但上线后更稳。这个问题的原因通常不是某一行代码写错而是页面状态、业务口径和数据回读没有分开。只要原因判断错了后面修起来就会变成哪里闪修哪里最后越修越乱。项目说明应用中式美食页面筛选弹窗技术栈HarmonyOS、ArkUI、ArkTS、本地数据主要问题状态口径不清楚页面显示和本地数据对不上处理目标用户重复操作、返回页面、刷新列表时结果保持一致环境版本/口径DevEco Studio6.1.0 ReleaseHarmonyOS SDKAPI 18页面模型ArkUI V2页面入口搜索页筛选弹窗验收重点取消不污染列表确认后才刷新结果为什么这个问题会出现很多页面刚开始能跑是因为测试路径太顺了点一次、等结果、再看页面。但真实使用里用户不会按我们的节奏来。用户会连续点会返回再进来会改条件后又取消也会在网络慢的时候以为自己没点上。常见写法当时看起来没问题后面的问题页面变量直接改代码短马上能看到效果Repository 失败后页面不知道怎么回滚不记录动作来源所有入口走同一段逻辑返回列表时不知道该刷新哪一块不做兜底校验单次测试能通过重复点击或旧数据会把状态弄乱只看 UI 不看数据页面像是对了下次进入又变成旧状态我现在写这类功能会先问自己一个问题这个状态到底是谁负责页面负责展示Repository 负责数据口径路由负责入口来源。只要这三件事混在一起后面必然难排查。页面层先把状态说清楚在 筛选弹窗 里我不会让一个布尔值承担所有含义。页面里至少要区分“正在处理”“当前展示结果”“已经确认的数据”。这样出问题时能知道到底是哪一步错了。typePageStatusidle|loading|saving|error;StateprivatepageStatus:PageStatusidle;StateprivatecurrentItems:RecipeItem[][];StateprivateerrorMessage:string;privatecanTriggerAction():boolean{returnthis.pageStatus!loadingthis.pageStatus!saving;}这段代码不是为了显得复杂而是为了避免页面状态互相抢。比如正在保存时不应该再触发同一个写动作加载失败时也不能直接展示空列表否则用户会以为真的没有数据。临时选中态和生效态要分开筛选弹窗最容易犯的错是用户点一个条件就直接改列表查询条件。这样用户点取消时页面其实已经变了。中式美食里我把筛选状态拆成两份弹窗里的临时态和页面真正生效的状态。interfaceRecipeFilter{keyword:string;cuisineType:string;difficulty:string;maxCookMinutes:number;}StateprivateappliedFilter:RecipeFilterthis.createDefaultFilter();StateprivatedraftFilter:RecipeFilterthis.createDefaultFilter();appliedFilter是列表真正使用的筛选条件draftFilter只在弹窗里使用。用户打开弹窗时把生效态复制一份给临时态用户点确认时再把临时态写回生效态。打开弹窗时复制不要共用引用如果直接让两个变量指向同一个对象临时态就不临时了。用户在弹窗里点一下列表条件也跟着变。privateopenFilterDialog():void{this.draftFilter{keyword:this.appliedFilter.keyword,cuisineType:this.appliedFilter.cuisineType,difficulty:this.appliedFilter.difficulty,maxCookMinutes:this.appliedFilter.maxCookMinutes};this.showFilterDialogtrue;}这里我宁愿多写几行拷贝也不偷懒直接赋值。筛选条件字段不多明确拷贝更好读也能避免引用串状态。确认和取消要走两条路径确认就是把draftFilter写回appliedFilter然后重新查询列表取消就是关闭弹窗不碰列表状态。privateasyncconfirmFilter():Promisevoid{this.appliedFilter{...this.draftFilter};this.showFilterDialogfalse;awaitthis.reloadRecipesByFilter();}privatecancelFilter():void{this.showFilterDialogfalse;this.draftFilter{...this.appliedFilter};}这段代码能解决“点了取消列表却变了”的问题。取消就是取消不应该偷偷刷新列表确认才是真的修改筛选结果。动作修改 draftFilter修改 appliedFilter是否刷新列表打开弹窗是否否弹窗内点条件是否否点取消丢弃临时态否否点确认是是是业务 key 要按真实含义来定筛选弹窗关闭后状态没恢复怎么办 不能靠一个全局变量糊过去。中式美食里我会给关键动作一个业务 key这个 key 必须能看懂它锁住的是哪件事。privatebuildActionKey(recipeId:string):string{returnappliedFilter:${recipeId};}privateasyncrunWithActionLock(recipeId:string,task:()Promisevoid):Promisevoid{constkeythis.buildActionKey(recipeId);if(this.runningKeys.has(key)){return;}this.runningKeys.add(key);try{awaittask();}finally{this.runningKeys.delete(key);}}这里的重点是finally。只要是异步动作就一定要考虑失败和异常。否则按钮锁住以后不释放用户只能退出页面重进。Repository 层再兜一次页面层能挡住大部分误操作但它不是最后防线。以后功能多了同一段 Repository 可能被详情页、列表页、服务卡片、导入逻辑一起调用。如果 Repository 自己不判断别的入口一样能写出脏数据。exportclassRecipeFilterState{asyncsave(recipeId:string,payload:Recordstring,string):Promisevoid{constoldRecordawaitthis.findByRecipeId(recipeId);if(oldRecord){awaitthis.update(oldRecord.id,{...payload,updatedAt:Date.now()});return;}awaitthis.insert({recipeId,...payload,createdAt:Date.now(),updatedAt:Date.now()});}}这段逻辑表达的是一个简单口径同一道菜相关的状态先看有没有旧记录。有就更新没有再插入。不要每次都新增否则数据会越来越脏。层级负责内容为什么不能省ArkUI 页面按钮、弹窗、列表展示让用户知道当前动作有没有进行中ViewModel组织页面状态和动作 key避免一个变量承担太多含义Repository查重、更新、插入防止其他入口绕过页面写脏数据验收用例重复点、返回、刷新、异常确认不是单次路径刚好成功返回页面后要读真实数据很多状态问题不是发生在当前页面而是发生在返回后。比如详情页里保存成功了列表页如果还拿旧数组显示用户看到的就是错的。asynconPageShow():Promisevoid{awaitthis.reloadVisibleState();}privateasyncreloadVisibleState():Promisevoid{constlatestawaitthis.repository.queryCurrentState();this.currentItemsthis.mergeState(this.currentItems,latest);}我不建议让详情页直接去改列表页内部数组。短期看省事长期看会让入口越来越乱。更稳的做法是写操作完成后相关页面自己回读一次需要展示的状态。为什么这样能解决问题这个方案能解决问题不是因为代码多写了几行而是因为每一层都有自己的责任。页面层解决的是“用户现在能不能继续点”的问题Repository 解决的是“这条业务数据到底应该新增还是更新”的问题页面返回后重新读取解决的是“别的页面看到的是不是最新结果”的问题。如果只做页面层重复点击少了但别的入口仍然可能写脏数据。如果只做 Repository数据是干净了但用户点按钮时没有反馈会以为应用卡住。如果只做返回刷新页面看起来会恢复但中间写错的数据已经进库了。三层合起来才是一个比较稳的处理方式。只做一层还能留下什么坑现在怎么避免只禁用按钮页面重建或其他入口可能绕过Repository 再查重和更新只查重入库用户连续点时没有反馈ArkUI 层给 busy 状态只返回刷新数据写错后只是重新显示错数据写入前先定业务口径只靠人工测试慢请求和旧数据覆盖测不出来固定重复点击和返回刷新用例以后怎么避免同类问题后面再写中式美食的页面我会先把动作分成两类只影响展示的动作和会改本地数据的动作。只影响展示的动作可以轻一点比如切换 tab、展开说明会改本地数据的动作就必须按上面的方式处理不能只写一个onClick。我的检查清单也会固定下来检查项不通过时会出现什么是否有业务 actionKey连续操作时不知道该拦哪一个动作是否有 finally 释放状态异常后按钮可能一直不可用Repository 是否查重别的入口会写出重复数据返回页面是否回读列表状态和详情页状态可能不一致失败提示是否具体用户不知道是保存失败还是页面失败这样做的好处是问题以后再出现时我不用从整页代码里乱找。先看 actionKey再看 Repository 口径再看返回刷新基本就能定位到是哪一层没有守住。还有一个容易忽略的小点不要把这类状态只写在注释里。真正有用的是把口径落到变量名、方法名和测试动作里。比如reloadVisibleState()比refresh()更清楚buildActionKey()比getKey()更清楚save()里面先findByRecipeId()再insert()也比直接写一串数据库调用更容易看懂。代码写清楚以后后面自己回来排查也快。看到列表乱了就先查 query 的排序看到按钮乱了就先查 actionKey看到数据重复了就先查 Repository 的查重逻辑。这个顺序固定下来排查效率会高很多。privateasyncverifyAfterAction(recipeId:string):Promisevoid{constpageStatethis.currentItems.find(itemitem.idrecipeId);constdbStateawaitthis.repository.findByRecipeId(recipeId);if(!pageState||!dbState){this.errorMessage页面状态和本地数据没有对上需要重新加载;awaitthis.reloadVisibleState();}}这段验收代码不一定要原样放到生产逻辑里但它提醒我每个会改数据的动作都要能回答“页面看到的”和“本地查到的”是不是同一件事。我会怎么验证只看一次正常点击没有意义必须按容易出错的路径测。验收动作预期结果快速连续触发同一个动作只有一次有效写入写入过程中返回再进入页面从 Repository 读到真实状态取消操作或失败重试临时状态不会污染正式结果旧数据缺少字段兜底排序或默认值不让页面崩刷新三次列表展示顺序和状态保持一致最后说一句筛选弹窗关闭后状态没恢复怎么办 表面是页面问题实际是状态边界问题。中式美食这类内容应用页面入口多、状态也多如果只靠一次点击后的临时变量后面一定会遇到列表刷新不对、按钮状态不对、数据重复的问题。我的做法就是把事情拆开页面负责当前交互ViewModel 负责动作节流和状态整理Repository 负责真实数据口径。这样以后功能继续加问题也能沿着这条线查不会变成到处猜。