当前位置: 首页 > news >正文

HarmonyOS PC实战之 一个 @State实现分类筛选

文章目录

  • 前言
      • 状态设计:最小化原则
      • 完整实现:PC端新闻筛选页
      • 为什么 get 比维护第二个数组好
      • 搜索和分类筛选叠加
      • 写在最后

前言

做带分类标签的新闻/内容列表,很多人的第一反应是维护两个数组:一个完整数据源,一个过滤后的展示列表。每次切换分类时,手动更新第二个数组。

这个方案能跑,但是多余的。ArkUI 有更简洁的做法:只维护activeCategory这一个状态,通过get访问器派生出过滤列表,模板里直接用派生属性。切换分类只需要改一个变量,其他的交给框架。

状态设计:最小化原则

先定义数据结构:

interfaceNewsItem{id:numbertitle:stringcategory:stringsource:stringtime:stringemoji:stringreadCount:number}

组件状态只需要一个:

@StateactiveCategory:string='全部'

派生属性用get

getfilteredNews():NewsItem[]{if(this.activeCategory==='全部')returnthis.allNewsreturnthis.allNews.filter(n=>n.category===this.activeCategory)}getcategories():string[]{constall=this.allNews.map(n=>n.category)return['全部',...newSet(all)]}

get categories()Set自动去重提取所有分类,新增数据时分类列表自动更新,不需要手动维护分类数组。

完整实现:PC端新闻筛选页

完整示例:PcNewsFilterPage.ets

import{router}from'@kit.ArkUI'interfaceNewsItem{id:numbertitle:stringdesc:stringcategory:stringsource:stringtime:stringemoji:stringreadCount:numberisLiked:boolean}@Entry@Componentstruct PcNewsFilterPage{@StateactiveCategory:string='全部'@StatelikedIds:number[]=[]@StatesearchText:string=''privateallNews:NewsItem[]=[{id:1,title:'HarmonyOS NEXT 发布:鸿蒙操作系统的里程碑',desc:'华为正式发布新一代鸿蒙操作系统,标志着全面去安卓化的完成。',category:'科技',source:'华为官方',time:'10分钟前',emoji:'📱',readCount:12600,isLiked:false},{id:2,title:'ArkUI 动效系统升级:新增弹簧动画和路径动画',desc:'最新版本的 ArkUI 框架带来了更丰富的动效能力。',category:'开发',source:'开发者社区',time:'1小时前',emoji:'✨',readCount:5600,isLiked:false},{id:3,title:'鸿蒙 PC 版适配指南正式上线',desc:'官方针对大屏设备发布完整适配规范,涵盖窗口管理与键盘适配。',category:'开发',source:'官方文档',time:'2小时前',emoji:'💻',readCount:4200,isLiked:false},{id:4,title:'鸿蒙生态应用数量突破十万',desc:'覆盖金融、出行、购物等主流场景,生态建设取得重大突破。',category:'产品',source:'鸿蒙生态',time:'3小时前',emoji:'🌐',readCount:8900,isLiked:false},{id:5,title:'智能家居设备接入鸿蒙生态实战',desc:'详解如何将 IoT 设备接入鸿蒙分布式能力框架。',category:'开发',source:'技术博客',time:'5小时前',emoji:'🏠',readCount:3100,isLiked:false},{id:6,title:'国产操作系统生态对比:2024年度盘点',desc:'鸿蒙、统信、麒麟……国产 OS 的竞争格局正在发生变化。',category:'科技',source:'科技评论',time:'1天前',emoji:'🔮',readCount:9800,isLiked:false},]getcategories():string[]{constcategories:string[]=['全部']this.allNews.forEach((news:NewsItem)=>{if(!categories.includes(news.category)){categories.push(news.category)}})returncategories}privategetFilteredNews():NewsItem[]{letresult:NewsItem[]=this.allNews??[]if(this.activeCategory!=='全部'){result=result.filter((n:NewsItem)=>n.category===this.activeCategory)}consttext=this.searchText??''if(text.trim()){constkeyword=text.trim().toLowerCase()result=result.filter((n:NewsItem)=>n.title.toLowerCase().includes(keyword)||n.desc.toLowerCase().includes(keyword))}returnresult}toggleLike(id:number){if(this.likedIds.includes(id)){this.likedIds=this.likedIds.filter(i=>i!==id)}else{this.likedIds.push(id)this.likedIds=this.likedIds.slice()}}@BuildercategoryTab(cat:string){Text(cat).fontSize(13).fontColor(this.activeCategory===cat?'#3B82F6':'#6B7280').fontWeight(this.activeCategory===cat?FontWeight.Medium:FontWeight.Normal).padding({left:14,right:14,top:7,bottom:7}).backgroundColor(this.activeCategory===cat?'#EFF6FF':Color.Transparent).borderRadius(20).border(this.activeCategory===cat?{width:1,color:'#BFDBFE'}:{width:0,color:Color.Transparent}).animation({duration:150,curve:Curve.EaseOut}).onClick(()=>{this.activeCategory=cat})}@BuildernewsCard(news:NewsItem){Column({space:8}){// 标题行Row({space:10}){Text(news.emoji).fontSize(20).width(36).height(36).textAlign(TextAlign.Center).backgroundColor('#F3F4F6').borderRadius(8)Column({space:4}){Text(news.title).fontSize(14).fontColor('#111827').fontWeight(FontWeight.Medium).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})Text(news.desc).fontSize(12).fontColor('#6B7280').maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})}.layoutWeight(1).alignItems(HorizontalAlign.Start)}// 底部元信息行Row(){Text(news.category).fontSize(10).fontColor('#3B82F6').backgroundColor('#EFF6FF').borderRadius(4).padding({left:6,right:6,top:2,bottom:2})Text(news.source).fontSize(11).fontColor('#9CA3AF').margin({left:8})Text('·').fontSize(11).fontColor('#D1D5DB').margin({left:4,right:4})Text(news.time).fontSize(11).fontColor('#9CA3AF')Blank()Text(`👁${news.readCount>999?(news.readCount/1000).toFixed(1)+'k':news.readCount}`).fontSize(11).fontColor('#9CA3AF').margin({right:12})Text(this.likedIds.includes(news.id)?'❤️':'🤍').fontSize(14).onClick(()=>this.toggleLike(news.id))}.width('100%')}.width('100%').padding({left:20,right:20,top:14,bottom:14}).backgroundColor(Color.White).border({width:{bottom:1},color:'#F9FAFB'})}build(){Column(){// 顶部搜索 + 分类标签Column({space:12}){// 搜索框Row({space:10}){Text('🔍').fontSize(15)TextInput({placeholder:'搜索文章标题或摘要',text:this.searchText}).layoutWeight(1).height(36).fontSize(13).backgroundColor(Color.Transparent).onChange((v)=>this.searchText=v)if(this.searchText){Text('✕').fontSize(13).fontColor('#9CA3AF').onClick(()=>this.searchText='')}}.width('100%').height(40).padding({left:12,right:12}).backgroundColor('#F3F4F6').borderRadius(20)// 分类标签横向滚动Scroll(){Row({space:6}){ForEach(this.categories,(cat:string)=>{this.categoryTab(cat)})}.padding({left:4,right:4})}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)}.padding({left:20,right:20,top:16,bottom:0}).backgroundColor(Color.White)// 结果数量提示Row(){Text(this.getFilteredNews().length>0?`${this.getFilteredNews().length}`:'没有找到相关文章').fontSize(13).fontColor('#9CA3AF')}.width('100%').padding({left:20,right:20,top:12,bottom:8})// 文章列表Scroll(){Column({space:0}){ForEach(this.getFilteredNews(),(news:NewsItem)=>{this.newsCard(news)})if(this.getFilteredNews().length===0){Column({space:12}){Text('🔍').fontSize(48)Text('暂无相关内容').fontSize(16).fontColor('#9CA3AF')Text('换个关键词试试?').fontSize(13).fontColor('#D1D5DB')}.width('100%').padding({top:80}).alignItems(HorizontalAlign.Center)}}}.layoutWeight(1).scrollBar(BarState.Off)}.width('100%').height('100%').backgroundColor('#F9FAFB')}}

为什么 get 比维护第二个数组好

维护两个数组的问题在于状态同步。每次添加/删除/修改数据,你需要同时更新原始数组和过滤数组,任何一处遗漏都会导致数据不一致。

get派生就没这个问题——派生属性是实时计算的,只要状态activeCategoryallNews变化,filteredNews下次访问时就自动重算。你只需要维护一份真实数据。

// ❌ 维护两个数组@StateallNews:NewsItem[]=[...]@StatefilteredNews:NewsItem[]=[...]// 重复数据,需要同步// ✅ 一个数组 + 一个派生privateallNews:NewsItem[]=[...]getfilteredNews():NewsItem[]{returnthis.allNews.filter(...)// 实时计算,永远准确}

搜索和分类筛选叠加

get filteredNews里把分类和关键词两个过滤条件串联:先按分类过滤,再按关键词过滤。

getfilteredNews():NewsItem[]{letresult=this.allNewsif(this.activeCategory!=='全部'){result=result.filter(n=>n.category===this.activeCategory)}if(this.searchText.trim()){constkeyword=this.searchText.trim().toLowerCase()result=result.filter(n=>n.title.toLowerCase().includes(keyword))}returnresult}

逻辑清晰,条件顺序不影响结果。如果后续要加排序、分页等,也只需要在这个get里继续处理,不影响其他任何地方。

写在最后

筛选页的状态设计原则很简单:状态存最小集合,其他全部派生activeCategory是最小集合,filteredNewscategories都是派生。少维护一个状态,就少一处出错的可能。

http://www.cnnetsun.cn/news/2947566.html

相关文章:

  • Bilibili-Evolved键盘快捷键深度解析:10个隐藏功能完全掌握
  • 2011年-2021年各省废气、废水污染物排放量统计数据
  • Umi-OCR:颠覆性离线文字识别工具,零门槛开启高效办公新时代
  • 136.深度学习优质毕设项目|标准DDPM扩散模型理论与工程落地全套
  • 深度实战:使用Legacy-iOS-Kit让经典iOS设备重焕新生
  • 稀宇科技 MiniMax 开源 M3 模型权重,发布 MSA 技术论文,输出速度大幅提升!
  • 30天自制操作系统终极指南:从零构建你的第一个操作系统
  • specs/features/DragAndDrop.spec.md中的测试用例
  • 泛型--列表
  • 浏览器用户画像分析-大屏数据接入
  • 5分钟掌握Forza Mods AIO:免费解锁地平线4/5的终极游戏体验
  • 具身智能数据采集成“铲子生意”:新创公司与大厂纷纷入局,2026年或迎规模化元年
  • 洛雪音乐音源终极配置指南:免费解锁全网无损音乐的5种方法
  • 企业级Windows日志监控系统:Visual Syslog Server终极解决方案
  • 如何在Visual Studio中实现专业级Markdown编辑体验:5分钟掌握Markdown Editor v2核心功能
  • 【小白也能轻松用】新手零基础学部署,OpenClaw2.6.4完整实操攻略(含最新安装包)
  • 探索开源MoeKoe音乐播放器:5个让你惊艳的纯净音乐体验
  • 猫抓浏览器插件:三步掌握网页媒体资源嗅探与下载终极指南
  • 终极指南:如何用uView-Plus快速构建Vue 3跨平台应用
  • 如何高效部署DG-Lab郊狼游戏控制器:打造专业直播互动体验
  • Docker快速入门上手教程(保姆式),含docker所有常用命令大全(详细)!
  • 自由度的本质:数据建模中的信息代价与约束逻辑
  • ESP32-WROOM-32e自动下载电路翻车实录:从CH340反接到IO2电平,我踩了三个坑
  • Windows下npx报错ENOENT?别慌,手动创建npm目录或修改prefix两步搞定
  • Vivado综合后时序总违例?别急着改代码,先看看Implementation报告里的这几点
  • Python邮件自动化实战:用smtplib+email库批量发通知,避开‘Connection closed’和协议坑
  • Windows 11系统优化指南:如何用开源工具提升51%性能
  • 从打印到智能文档:clawPDF虚拟打印机终极指南
  • Node.js项目依赖安装卡住?可能是系统时间在捣鬼!手把手教你排查和修复CERT_HAS_EXPIRED
  • PostgreSQL高可用管理平台哪个好?为什么越来越多企业开始关注CLup?