鸿蒙ArkUI实战:用派生数据组合任务筛选与关键词搜索

📅 发布时间:2026/7/12 1:19:41 👁️ 浏览次数:
鸿蒙ArkUI实战:用派生数据组合任务筛选与关键词搜索
前言为什么“先点筛选再搜索”最容易暴露逻辑问题待办列表只有筛选时很多人会写一个“进行中数组”只有搜索时又写一个“搜索结果数组”。两个功能单独演示都能跑一旦用户先点“进行中”再输入关键词或先搜索再切到“已完成”结果就开始不稳定搜索结果覆盖了筛选结果清空搜索词后列表无法恢复顶部数量和实际卡片数对不上。这类问题的根源不是条件判断写得不够多而是把“结果”当成了需要保存的数据。筛选和搜索本质上都是查询条件任务源数据不变用户只是改变了观察角度。正确做法是保存条件把结果从同一个方法中重新推导。本文以当前鸿蒙 ArkUI 待办面板为例完整拆解getVisibleTodos()的写法。它把完成状态筛选和关键词搜索组合为一次过滤并让列表、摘要和空状态共同依赖这一结果。这样无论用户怎样切换操作顺序页面都有唯一答案。一、先确定三个输入和一个输出组合筛选前先明确输入。当前页面的任务源数据是todos用户可改变的条件是filter和keywordtypeTodoFilter全部|进行中|已完成;Stateprivatetodos:TodoItem[][];Stateprivatefilter:TodoFilter全部;Stateprivatekeyword:string;输出只有一个当前应该渲染到列表中的任务数组。它不是新的State而是每次根据上述输入计算出来的结果。todos filter keyword ↓ getVisibleTodos() ↓ 列表 / 当前数量 / 空状态这个结构看似普通却避免了大量分支。筛选按钮不再负责“把列表换成什么”它只负责更新filter搜索框不再负责“保存搜索结果”它只负责更新keyword。真正决定列表内容的规则集中在同一个位置。二、把筛选条件写成可读的布尔变量当前工程中的核心方法如下privategetVisibleTodos():TodoItem[]{constquerythis.keyword.trim().toLowerCase();returnthis.todos.filter((item:TodoItem):boolean{constmatchesFilterthis.filter全部||(this.filter进行中!item.done)||(this.filter已完成item.done);constmatchesKeywordquery.length0||item.title.toLowerCase().includes(query)||item.category.toLowerCase().includes(query);returnmatchesFiltermatchesKeyword;});}不要被它放在一个方法里迷惑内部其实分成四步。第一步是规范化关键词trim()去掉用户输入前后的空白toLowerCase()让英文大小写比较更稳定。当前示例任务是中文为主大小写处理对中文没有副作用如果以后允许英文分类和标题它能避免Task与task被当成两个词。第二步是计算matchesFilter。全部不限制完成状态进行中要求!item.done已完成要求item.done。把逻辑拆成有名字的变量比在return后面写一长串条件更容易阅读也方便以后增加“高优先级”“今天到期”等新筛选。第三步是计算matchesKeyword。关键词为空时所有任务都匹配不为空时标题或分类只要有一个包含查询词就视为命中。这里选择“标题 OR 分类”是因为用户在待办面板中往往记得任务名称也可能只记得它属于“内容”或“验证”。第四步才是组合matchesFilter matchesKeyword。这意味着一项任务必须同时满足状态和搜索要求。使用是本篇最关键的语义如果错写成||一个已完成但标题命中的任务会出现在“进行中”页签中筛选规则就被破坏了。三、为什么结果不能再保存一次下面是常见但不推荐的思路StateprivatefilteredTodos:TodoItem[][];StateprivatesearchedTodos:TodoItem[][];这样设计后用户每次输入字符都要决定搜索哪一份数组切换筛选时又要决定是否在搜索结果上再筛勾选任务后还要更新至少两份结果。结果数组越来越多真实数据源反而变得不清楚。派生方法没有这个问题。getVisibleTodos()永远从todos出发条件改变后直接重算。它不是缓存因此不存在“缓存忘记更新”的风险。对于当前的少量任务这种遍历成本很小换来的是可靠的行为和更短的状态链。如果未来列表非常长优化顺序也不应颠倒。先通过实际分析确认计算真的成为瓶颈再考虑节流输入、分页、索引或缓存策略即使加入优化也要确保缓存的失效条件覆盖todos、filter、keyword三个输入。没有测量就提前维护两份列表通常只会把复杂度从性能问题变成正确性问题。四、让按钮和输入框只修改自己的条件筛选项的点击事件足够简单.onClick(():void{this.filterfilter;});搜索框同样只写入关键词TextInput({placeholder:搜索任务或分类,text:this.keyword}).onChange((value:string):void{this.keywordvalue;});这两个事件不调用列表渲染方法、不修改任务数组、不手动清空别的条件。保留搜索词再切换筛选是一个有意识的选择用户可能正试图查看“包含文章的已完成任务”切换页签后仍保留关键词符合连续操作的预期。如果产品希望切换筛选时清空搜索也可以明确执行this.keyword 但那应该是可说明的交互规则而不是由于状态混乱产生的副作用。五、同一个派生结果必须被多个区域复用只让列表使用getVisibleTodos()还不够。顶部摘要和空状态如果改用其他算法仍可能与列表不一致。当前页面将它们统一在同一来源上Text(当前${this.getVisibleTodos().length}项尚有${this.getRemainingTodoCount()}项待完成)if(this.getVisibleTodos().length0){Column(){Text(没有匹配的任务)Text(试试清空搜索词、切换筛选条件或新建一条待办。)}}else{List(){ForEach(this.getVisibleTodos(),(item:TodoItem):void{ListItem(){this.TodoCard(item);}},(item:TodoItem):stringtodo-${item.id});}}“当前 N 项”和“尚有 N 项待完成”并不是同一种数字。前者来自可见结果后者来自整个源数组。把两者并排展示能减少误解用户在“已完成”里搜索时当前可见数可能是 1而全局待完成数仍是 2这并不矛盾。空状态也必须依赖可见结果而不是todos.length。只要任务总数大于零却当前条件无匹配项就应该展示“没有匹配的任务”而不是让页面空白或错误显示“暂无任务”。六、筛选与搜索组合时的操作顺序验证判断实现是否可靠不能只测试一种顺序。建议至少覆盖以下几组操作。1. 先筛选再搜索进入“进行中”再输入某个标题的一部分。结果必须同时是未完成且文本命中的任务。若已完成项混进来检查状态条件是否被||放宽。2. 先搜索再筛选先输入“内容”再切换“已完成”。关键词应保留列表在原始任务上重新按完成状态计算。若切换后出现与关键词无关的任务说明筛选按钮绕开了统一的派生方法。3. 搜索无结果再清空输入一个不存在的词出现空状态清空输入后原任务恢复。这个用例专门检查搜索是否误删或覆盖了todos。4. 在结果中完成任务处于“进行中 关键词”条件时勾选一项任务应立即从当前结果消失切到“已完成”且关键词仍命中时应能看到该任务。这个用例验证任务写入和派生查询是否真正串联。5. 新增任务后保留还是重置条件当前addTodo()明确把筛选切回“全部”搜索词不自动改动。新增任务是否可见取决于它是否匹配现有关键词。这种行为可以根据产品需要调整但必须在代码里明确而不要让它由多个结果数组的偶然顺序决定。七、为后续筛选条件扩展留好接口以后要增加“高优先级”“今天到期”“某一分类”不必再建新的列表状态。可以继续给派生方法增加命名清楚的条件constmatchesPrioritythis.onlyHighPriority?item.priority高:true;constmatchesCategorythis.category全部||item.categorythis.category;returnmatchesFiltermatchesKeywordmatchesPrioritymatchesCategory;前提是每个条件都有明确归属onlyHighPriority、category是页面查询状态priority、item.category是任务事实。条件更多时可以再抽成独立函数或把查询条件组织成对象但“只保存输入、统一推导输出”的原则不变。当前模拟器画面这张图对应当前模拟器已验证的运行场景它不替代签名、发布链接或未实现功能的验证。小结筛选和搜索不是两套列表功能而是对同一份任务数据施加的两类条件。当前待办面板用getVisibleTodos()把它们组合为一次查询并让列表、数量和空状态都复用该结果。当用户操作顺序变复杂时这种设计仍然稳定按钮只改筛选输入框只改关键词勾选只改任务事实界面只读派生结果。先建立这条单向关系后续再增加更多筛选条件也不会让页面陷入“结果覆盖结果”的循环。