ArkUI 搜索框输入太快怎么办:中式美食怎么把输入态、请求版本和空结果分开

发布时间:2026/7/17 3:39:45
ArkUI 搜索框输入太快怎么办:中式美食怎么把输入态、请求版本和空结果分开 问题不是“搜索慢”而是旧结果回来得太晚中式美食里有一个很常见的搜索场景用户想找“番茄牛腩”他不会慢慢输完再等结果而是边输边看。这个时候如果代码写得粗一点就会出现一个很别扭的现象明明输入框里已经是“番茄”下面列表却闪回了“番”的结果或者用户清空关键词以后空结果提示还停在旧关键词上。这类问题看起来像接口慢其实更像状态没拆开。输入框里的文字是一种状态正在执行的请求是一种状态展示出来的结果又是另一种状态。如果三者用一个变量糊在一起页面越快响应越容易乱。场景容易出的问题快速输入旧关键词结果覆盖新关键词快速删除空结果提示对应的是上一次搜索切分类后搜索分类条件和关键词没有一起参与版本判断从详情页返回搜索词还在但列表被默认推荐覆盖输入态先独立出来搜索框的值要立刻更新不要等请求结束才改。用户输入时页面应该马上显示他输入了什么至于什么时候查 RDB可以交给防抖。typeSearchUiState{inputText:stringcommittedKeyword:stringloading:booleanemptyKeyword:stringrecipes:RecipeItem[]}inputText和committedKeyword分开以后页面就不会因为防抖等待而显得卡。输入框显示inputText真正触发搜索时才把它提交成committedKeyword。防抖只解决“少触发”不解决“旧请求”很多搜索问题只加一个 debounce看起来能缓解但不彻底。防抖只能减少请求次数不能保证最后回来的一定是最新请求。所以还要有请求版本。classSearchViewModel{privaterequestVersion0privatetimer?:numberonInputChanged(text:string){this.state.inputTexttextclearTimeout(this.timer)this.timersetTimeout((){this.commitSearch(text)},280)}}这段只负责“别每按一个字都查”。它不负责判断结果是不是过期。请求版本用来挡住旧结果真正保证结果不串的是requestVersion。每次提交搜索都生成一个新版本RDB 查询回来以后先检查版本是不是当前版本。asynccommitSearch(keyword:string){constversionthis.requestVersionconstnormalizedkeyword.trim()this.state.committedKeywordnormalizedthis.state.loadingtrueconstresultnormalized?awaitthis.repository.searchByKeyword(normalized):awaitthis.repository.queryDefaultRecipes()if(version!this.requestVersion){return}this.state.loadingfalsethis.state.recipesresultthis.state.emptyKeywordresult.length0?normalized:}这里有一个细节空结果也要带关键词。否则用户先搜“西红柿”再改成“番茄”旧请求回来时可能会把“西红柿没有结果”显示在“番茄”下面。RDB 查询也要吃到同一份关键词Repository 不应该猜页面状态它只接收明确的关键词和分类条件。这样搜索页、分类页、推荐页都可以复用但不会互相污染。asyncsearchByKeyword(keyword:string):PromiseRecipeItem[]{constkeykeyword.trim()if(!key)return[]constpredicatesnewrelationalStore.RdbPredicates(recipe).beginWrap().like(name,%${key}%).or().like(alias,%${key}%).or().like(ingredients_text,%${key}%).endWrap().orderByDesc(score).limit(30)returnthis.mapper.fromResultSet(awaitthis.rdb.query(predicates))}我不建议在 Repository 里直接读页面输入框。Repository 只认入参这样才方便测试也方便以后把搜索改成“历史词 热门食材 菜谱结果”的组合搜索。空结果不要覆盖加载态空结果经常写错。加载中时不应该显示“没找到”只有当前版本查询完成且结果为空才显示空状态。BuilderSearchResult(){if(this.state.loading){LoadingRow({text:正在找相关菜谱})}elseif(this.state.emptyKeyword){EmptySearch({keyword:this.state.emptyKeyword})}else{RecipeList({recipes:this.state.recipes})}}这段看起来简单但能避免很多用户肉眼能看到的闪烁加载中不是空旧关键词的空也不是当前关键词的空。我用这些动作压了一遍操作预期结果快速输入“番茄牛腩”最终只展示完整关键词对应结果输入后马上删除不显示旧关键词空结果搜索中点进详情再返回搜索词和结果列表保持一致切换分类后继续搜索分类条件和关键词一起生效搜不到菜谱空状态展示当前关键词不拿旧词凑数最后这个问题我会记成一个原则输入框负责快搜索请求负责准结果展示负责稳。三件事不要混成一个变量。中式美食后面如果继续做搜索联想、历史词、热门食材和推荐补全也要沿着这个边界往下拆不然搜索体验一定会越改越乱。为什么不能把输入框和搜索结果绑死搜索页最容易写成一个变量控制全部状态输入框改了立刻拿这个值查查询回来直接把结果和空状态都写回页面。这个写法在 demo 里没问题因为 demo 操作慢、数据少、请求快。但中式美食的真实使用不是这样用户会连续输入、删除、点历史词、切分类还会从详情页再回来。写法短期看起来的好处真正的问题输入一次查一次代码直观请求太密旧结果容易回流一个 keyword 管全部状态少输入态和已提交搜索态分不清空数组就显示空状态判断简单加载中和旧关键词空结果会误判Repository 自己读页面值调用省事复用和测试都变困难所以我把输入态、提交态、请求态、结果态拆开。这样写多了几个字段但每个字段都能解释清楚自己负责什么。出问题时也容易定位输入框错了看inputText旧请求回来错了看requestVersion空状态错了看emptyKeyword。为什么空结果也要记住关键词空结果不是一个简单布尔值。用户搜“西红柿”没有结果和搜“番茄”没有结果是两件事。如果页面只存isEmptytrue旧请求回来以后很可能把旧关键词的空状态显示在新关键词下面。状态说明loadingtrue现在还不能判断有没有结果recipes.length 0当前关键词有结果emptyKeyword committedKeyword当前关键词确实没结果emptyKeyword ! committedKeyword旧空状态不能展示这个判断看起来细但用户能直接感知。搜索页最怕的不是慢一两百毫秒而是结果和输入框对不上。只要对不上用户就会怀疑搜索不准。搜索联想以后也能沿用这套边界中式美食后面如果加历史词、热门食材、菜谱结果三段式搜索也不需要推翻这套逻辑。输入框还是输入框防抖还是防抖请求版本还是请求版本只是结果模型从一个数组变成多个分组。typeSearchPanelState{inputText:stringcommittedKeyword:stringloading:booleanhistoryWords:string[]hotIngredients:string[]recipeResults:RecipeItem[]emptyKeyword:string}展示层可以按分组渲染但版本判断仍然只有一套。这样不会出现“历史词是新关键词菜谱列表还是旧关键词”的混乱状态。functioncanApplySearchResult(version:number,currentVersion:number,keyword:string,currentKeyword:string){returnversioncurrentVersionkeywordcurrentKeyword}我会把这个函数写得很普通不追求炫技。搜索这种交互稳定比花哨更重要。以后怎么避免再踩新增搜索能力先检查什么搜索历史点击历史词是否更新 inputText 和 committedKeyword热门食材点食材后是否走同一条搜索提交逻辑分类内搜索分类条件是否进入版本判断搜索结果分页下一页是否继承当前 committedKeyword返回列表搜索词、结果和滚动位置是否一起恢复最后还有一个验收习惯搜索功能不能只测“输入完整关键词后等结果”。一定要测快速输入、快速删除、点历史词、点热门食材、清空搜索、搜索中返回详情。这些动作都能稳才说明搜索状态没有混在一起。