ArkUI 筛选清空后列表还乱怎么办:中式美食关键词、分类和排序状态怎么收口
用户先搜“牛肉”,再点“家常菜”,又切到“收藏优先”,最后点“清空”。如果页面还保留旧关键词,或者列表标题显示已清空但结果还是分类后的数据,这个体验会很糟。中式美食的搜索、分类、推荐、收藏都会碰到类似问题。ArkUI 页面写起来很快,但筛选状态如果散在多个组件里,后面一定会出现“清空了却没真清空”的问题。
| 应用名称 | 中式美食 |
|---|---|
| 这篇解决什么 | 关键词、分类、排序、分页混在一起后,清空筛选不彻底 |
| 谁会搜这篇 | 做 HarmonyOS 列表页、搜索页、分类页、筛选条的 ArkUI 开发者 |
| 点进来能拿到什么 | 筛选状态模型、reset 规则、ViewModel 输出和验收用例 |
| 当前项目证据 | SearchViewModel、搜索历史/热门词/结果状态、购物清单列表状态 |
| HarmonyOS 目标能力 | ArkUI、ArkTS、ViewModel、ForEach、List、状态收口 |
本章导读
这篇不讲花哨筛选组件,只讲一个真实问题:清空筛选后列表到底应该回到什么状态。中式美食这种应用,列表入口多,筛选条件也多。关键词、分类、排序、分页、空状态如果各管各的,页面很快就会出现互相打架。
| 章节 | 重点 |
|---|---|
| 问题从哪里来 | 为什么组件里各存一份状态会乱 |
| 状态模型怎么定 | 用一个FilterState收口关键词、分类、排序 |
| reset 怎么写 | 清空不是只清输入框,还要清分页和空态 |
| ArkUI 怎么接 | 页面只展示状态,不自己拼条件 |
| 怎么验收 | 搜索、分类、排序、清空、返回都要测 |
当前验证环境和技术栈
| 项目 | 说明 |
|---|---|
| DevEco Studio | 6.x 系列 |
| HarmonyOS SDK | API 12/ArkTS 工程 |
| UI 层 | ArkUI 声明式页面 |
| 状态层 | ViewModel 统一输出列表状态 |
| 当前项目参考 | 中式美食搜索页、分类列表、推荐流、购物清单 |
| 验收方式 | 固定操作链路:搜索、分类、排序、清空、返回 |
当前中式美食搜索 ViewModel 已经把历史词、热门词和搜索结果分成不同状态。这个方向是对的。下一步要补的是筛选条件本身也要收口。
Future<void>search(Stringkeyword,{int page=1})async{state=state.copyWith(keyword:keyword,results:constUiState.loading());try{finalresult=await_repository.search(keyword,page:page);state=state.copyWith(results:result.items.isEmpty?constUiState.empty('没有找到相关菜谱'):UiState.success(result),);}catch(error){state=state.copyWith(results:UiState.error(error.toString()));}}如果后面关键词、分类、排序都各自写一个@State,清空按钮就要知道所有状态在哪里,漏一个就出问题。
先把筛选状态合成一个对象
我会先定一个RecipeFilterState。关键词、分类、排序、页码、是否只看收藏,都放在一处。页面组件不再各自保存业务条件。
typeRecipeSortMode='default'|'recent'|'favorite'|'cookTime';interfaceRecipeFilterState{keyword:string;categoryId:string;sortMode:RecipeSortMode;onlyFavorite:boolean;page:number;}constdefaultFilterState:RecipeFilterState={keyword:'',categoryId:'',sortMode:'default',onlyFavorite:false,page:1};| 字段 | 什么时候变 | 清空时怎么处理 |
|---|---|---|
keyword | 用户输入或点历史词 | 回到空字符串 |
categoryId | 用户点分类 | 回到全部分类 |
sortMode | 用户切排序 | 回到默认排序 |
onlyFavorite | 用户只看收藏 | 回到 false |
page | 分页加载 | 回到第一页 |
这里最容易漏的是page。很多列表清空后还停在第 3 页,结果用户看到的不是默认列表前几条,而是默认条件下的第 3 页。
reset 不是只清输入框
清空按钮必须重置整份筛选状态,并重新拉取默认列表。不要只把输入框清空。
@ObservedexportclassRecipeListViewModel{filter:RecipeFilterState={...defaultFilterState};items:RecipeCardModel[]=[];loading:boolean=false;emptyText:string='';asyncresetFilter(){this.filter={...defaultFilterState};awaitthis.reload();}}如果只是这样还不够,reload也要只读当前filter,不要让页面把临时条件传来传去。
asyncreload(){this.loading=true;this.emptyText='';try{constresult=awaitthis.repository.query(this.filter);this.items=result.items;this.emptyText=result.items.length===0?'没有找到符合条件的菜谱':'';}finally{this.loading=false;}}| 操作 | 错误写法 | 稳定写法 |
|---|---|---|
| 清空关键词 | 只改输入框文字 | 重置整份filter |
| 切分类 | 分类组件自己存 id | ViewModel 更新filter.categoryId |
| 切排序 | 排序组件单独请求 | ViewModel 更新filter.sortMode后 reload |
| 翻页 | 页面自己拼 page | ViewModel 控制filter.page |
| 返回列表 | 重新造默认状态 | 保留 ViewModel 当前状态 |
ArkUI 组件只触发动作
页面组件不应该自己拼筛选条件。组件只负责把用户动作告诉 ViewModel。
@Componentstruct RecipeFilterBar{@ObjectLinkvm:RecipeListViewModel;build(){Row(){TextInput({text:this.vm.filter.keyword,placeholder:'搜索菜名或食材'}).onSubmit((value:string)=>this.vm.changeKeyword(value))Button('清空').onClick(()=>this.vm.resetFilter())}}}分类和排序也同理。
asyncchangeCategory(categoryId:string){this.filter={...this.filter,categoryId,page:1};awaitthis.reload();}asyncchangeSort(sortMode:RecipeSortMode){this.filter={...this.filter,sortMode,page:1};awaitthis.reload();}每次变更筛选条件都把page拉回 1,这样用户不会被旧分页影响。
列表渲染只相信 ViewModel 输出
列表组件只拿items,不要再二次过滤。二次过滤会让标题、空状态和数据对不上。
@Componentstruct RecipeList{@ObjectLinkvm:RecipeListViewModel;build(){List(){ForEach(this.vm.items,(item:RecipeCardModel)=>{ListItem(){RecipeCard({item})}},(item:RecipeCardModel)=>item.id)}}}| 页面显示 | 来源 | 不要怎么做 |
|---|---|---|
| 列表数据 | vm.items | 组件里再 filter 一遍 |
| 空文案 | vm.emptyText | 页面自己猜空态原因 |
| 当前关键词 | vm.filter.keyword | TextInput 单独维护一份 |
| 当前分类 | vm.filter.categoryId | 分类组件单独存 |
| 当前排序 | vm.filter.sortMode | 排序弹窗自己请求数据 |
请求乱序也要防
用户快速输入和切分类时,很容易出现后发请求先回来。这个问题不处理,列表会被旧结果覆盖。
letrequestVersion=0;asyncreload(){constcurrent=++requestVersion;this.loading=true;constresult=awaitthis.repository.query(this.filter);if(current!==requestVersion){return;}this.items=result.items;this.loading=false;}这不是复杂优化,是列表页基本防线。搜索、分类、排序组合越多,越需要请求版本号。
工程验收
| 验收项 | 操作 | 通过标准 |
|---|---|---|
| 关键词搜索 | 输入“牛肉” | 列表只显示命中结果 |
| 分类切换 | 点“家常菜” | 标题、列表、空态都对应家常菜 |
| 排序切换 | 切“收藏优先” | 列表顺序变化,筛选条件不丢 |
| 清空筛选 | 点清空 | 关键词、分类、排序、分页都回默认 |
| 快速切换 | 连续输入和点分类 | 最终只显示最后一次条件结果 |
| 返回列表 | 从详情页返回 | 原筛选状态仍在 |
可以把验收写成下面这种检查:
functionassertDefaultFilter(filter:RecipeFilterState){if(filter.keyword!==''){thrownewError('清空后 keyword 没有归零');}if(filter.categoryId!==''){thrownewError('清空后 categoryId 没有归零');}if(filter.sortMode!=='default'){thrownewError('清空后 sortMode 没有回默认');}if(filter.page!==1){thrownewError('清空后 page 没有回第一页');}}常见问题和边界
| 问题 | 我的处理 |
|---|---|
| 筛选状态要不要进 Preferences | 高频恢复可以进,比如最后一次分类;普通临时筛选先留 ViewModel |
| 清空后要不要保留搜索历史 | 搜索历史可以保留,但当前关键词必须清空 |
| 返回详情后要不要重新请求 | 先保留原列表,再按需后台刷新 |
| 空态文案放哪里 | ViewModel 输出,页面不自己判断 |
| 分类和关键词能不能同时生效 | 可以,但规则必须明确,不能组件各自决定 |
本章小结
中式美食的列表筛选,真正要解决的是“状态只有一个可信来源”。关键词、分类、排序、分页和空态如果散在多个组件里,清空按钮永远容易漏。看起来只是一个小交互,实际会影响搜索页、分类页、推荐流、收藏页和详情返回。
我的做法是把筛选条件合成RecipeFilterState,所有入口都通过 ViewModel 改它;页面只触发动作和展示结果;清空时重置整份状态,并把分页拉回第一页。这样写不花哨,但可以让用户明确感到:我点了什么,页面就按什么变;我点清空,页面就真的回到默认。
如果你也在做 HarmonyOS 列表页,可以先把所有筛选条件列出来,然后只允许一份状态说了算。这个规则定住,后面的 UI 才不会越写越乱。
