HarmonyOS7 待办状态管理页实战:用 @State 把页面写活
2026/7/18 13:16:11 网站建设 项目流程

文章目录

      • 先从“谁会变化”开始看
      • 列表更新要返回新数组
      • getter 让页面少写临时变量
      • `@State` 适合页面内部,不适合所有场景
      • 深色模式也是状态驱动的例子
      • 完整代码
      • 写在最后

@State是 ArkUI 里最常见的状态装饰器,也最容易被低估。很多人第一次用它,只会写一个计数器;但真正做页面时,它要同时撑住输入框、列表、筛选、进度、主题切换这些交互。

这个待办页就很适合练手:功能不复杂,但状态变化非常密集。

先从“谁会变化”开始看

页面里会变化的东西很多:计数器数字、输入框内容、深色模式、待办列表、是否只看未完成。它们都属于当前页面内部状态,所以用@State很合适。

TodoItem_3hb0只描述一条任务:id、文本、完成状态和优先级。数据模型越简单,后面的增删改查越不容易乱。

列表更新要返回新数组

待办列表最典型的操作是切换完成状态。这里没有直接改原数组里的对象,而是用map()生成一个新数组,再赋值回this.todos

toggleTodo(id:number){this.todos=this.todos.map((t:TodoItem_3hb0)=>{if(t.id===id){return{id:t.id,text:t.text,done:!t.done,priority:t.priority}}returnt})}

这个写法很重要。状态驱动 UI 的核心不是“数据变了就行”,而是让框架明确感知到状态引用发生了变化。addTodo()concat()deleteTodo()filter(),也是同一个思路。

getter 让页面少写临时变量

filteredTodos根据filterDone返回全部任务或未完成任务;doneCount根据todos计算完成数量。它们不是额外状态,而是由已有状态推出来的数据。

这类值适合写成 getter,避免手动维护两份数据。否则添加、删除、切换完成状态时,还要同步更新统计字段,很容易漏。

@State适合页面内部,不适合所有场景

这个案例里所有状态都只服务StateTodoPage,所以用@State很自然。但如果待办列表要被多个子组件共同修改,或者跨页面共享,就不应该继续硬塞在一个组件里。那时要考虑@Link@Provide/@Consume,或者更完整的状态管理方案。

我会把@State理解成页面内部的“单组件状态源”:轻、直接、反馈快,但边界要守住。

深色模式也是状态驱动的例子

isDarkMode不只是换一个图标,它影响标题颜色、背景色、列表分割线、输入框样式。这个例子很好地说明了:UI 样式也可以由状态驱动,不一定只用状态控制数据。

完整代码

下面是整理后的完整代码,入口组件名已经换成StateTodoPage,可以直接按项目路由规则接入。

// @State 状态管理基础interfaceTodoItem_3hb0{id:numbertext:stringdone:booleanpriority:number}@Entry@Componentstruct StateTodoPage{// @State 基础用法:触发组件重新渲染@Statecount:number=0@StateinputText:string=''@StateisDarkMode:boolean=false@Statetodos:TodoItem_3hb0[]=[{id:1,text:'学习 ArkUI 布局组件',done:true,priority:2},{id:2,text:'完成项目需求评审',done:false,priority:1},{id:3,text:'阅读鸿蒙开发文档',done:false,priority:2},{id:4,text:'编写技术分享文章',done:false,priority:3},]@StatefilterDone:boolean=falsegetfilteredTodos():TodoItem_3hb0[]{if(this.filterDone){returnthis.todos.filter((t:TodoItem_3hb0)=>!t.done)}returnthis.todos}getdoneCount():number{returnthis.todos.filter((t:TodoItem_3hb0)=>t.done).length}toggleTodo(id:number){this.todos=this.todos.map((t:TodoItem_3hb0)=>{if(t.id===id){return{id:t.id,text:t.text,done:!t.done,priority:t.priority}}returnt})}addTodo(){if(this.inputText.trim().length===0)returnconstnewTodo:TodoItem_3hb0={id:Date.now(),text:this.inputText,done:false,priority:2}this.todos=this.todos.concat([newTodo])this.inputText=''}deleteTodo(id:number){this.todos=this.todos.filter((t:TodoItem_3hb0)=>t.id!==id)}priorityLabel(p:number):string{if(p===1)return'高'if(p===2)return'中'return'低'}priorityColor(p:number):string{if(p===1)return'#EF4444'if(p===2)return'#D97706'return'#9CA3AF'}priorityBg(p:number):string{if(p===1)return'#FEE2E2'if(p===2)return'#FEF3C7'return'#F3F4F6'}build(){Column({space:0}){// 顶部栏Row(){Column({space:2}){Text('@State 状态管理').fontSize(20).fontWeight(FontWeight.Bold).fontColor(this.isDarkMode?'#FFFFFF':'#111827')Text('状态驱动视图自动更新').fontSize(12).fontColor(this.isDarkMode?'#9CA3AF':'#6B7280')}.alignItems(HorizontalAlign.Start).layoutWeight(1)Row({space:12}){Text(this.isDarkMode?'☀️':'🌙').fontSize(22).onClick(()=>{this.isDarkMode=!this.isDarkMode})}}.width('100%').padding({left:20,right:20,top:16,bottom:14}).backgroundColor(this.isDarkMode?'#1F2937':'#FFFFFF')// 计数器(最直观的 @State 演示)Row({space:20}){Button('−').fontSize(22).width(44).height(44).borderRadius(22).fontColor(this.isDarkMode?'#FFFFFF':'#374151').backgroundColor(this.isDarkMode?'#374151':'#F3F4F6').onClick(()=>{this.count--})Text(this.count.toString()).fontSize(36).fontWeight(FontWeight.Bold).fontColor(this.count>0?'#2563EB':this.count<0?'#EF4444':(this.isDarkMode?'#FFFFFF':'#111827')).width(80).textAlign(TextAlign.Center)Button('+').fontSize(22).width(44).height(44).borderRadius(22).fontColor(this.isDarkMode?'#FFFFFF':'#374151').backgroundColor(this.isDarkMode?'#374151':'#F3F4F6').onClick(()=>{this.count++})Button('重置').fontSize(12).borderRadius(16).fontColor('#9CA3AF').backgroundColor('transparent').padding({left:8,right:8,top:4,bottom:4}).onClick(()=>{this.count=0})}.width('100%').justifyContent(FlexAlign.Center).padding({top:16,bottom:16}).backgroundColor(this.isDarkMode?'#111827':'#F8FAFF').alignItems(VerticalAlign.Center)// Todo 列表主体Column({space:0}){// 进度统计Column({space:8}){Row(){Text(`已完成${this.doneCount}/${this.todos.length}`).fontSize(13).fontColor(this.isDarkMode?'#9CA3AF':'#6B7280')Blank()Row({space:6}){Text(this.filterDone?'显示全部':'只看未完成').fontSize(12).fontColor(this.isDarkMode?'#60A5FA':'#2563EB').onClick(()=>{this.filterDone=!this.filterDone})}}.width('100%')Progress({value:this.doneCount,total:this.todos.length,type:ProgressType.Linear}).color('#2563EB').backgroundColor(this.isDarkMode?'#374151':'#E5E7EB').style({strokeWidth:6})}.padding({left:20,right:20,top:12,bottom:12}).backgroundColor(this.isDarkMode?'#1F2937':'#FFFFFF')// 输入框Row({space:10}){TextInput({placeholder:'添加新任务...',text:this.inputText}).fontSize(14).fontColor(this.isDarkMode?'#FFFFFF':'#111827').placeholderColor('#9CA3AF').backgroundColor(this.isDarkMode?'#374151':'#F9FAFB').borderRadius(8).layoutWeight(1).onChange((val:string)=>{this.inputText=val}).onSubmit(()=>this.addTodo())Button('添加').fontSize(13).borderRadius(8).fontColor(Color.White).backgroundColor(this.inputText.length>0?'#2563EB':'#9CA3AF').padding({left:14,right:14,top:10,bottom:10}).onClick(()=>this.addTodo())}.padding({left:20,right:20,top:10,bottom:10}).backgroundColor(this.isDarkMode?'#1F2937':'#FFFFFF')// Todo 列表Scroll(){Column({space:0}){ForEach(this.filteredTodos,(todo:TodoItem_3hb0)=>{Row({space:12}){Text(todo.done?'✅':'⬜').fontSize(22).onClick(()=>this.toggleTodo(todo.id))Column({space:4}){Text(todo.text).fontSize(14).fontColor(todo.done?(this.isDarkMode?'#6B7280':'#9CA3AF'):(this.isDarkMode?'#FFFFFF':'#111827')).decoration({type:todo.done?TextDecorationType.LineThrough:TextDecorationType.None})}.layoutWeight(1)Text(this.priorityLabel(todo.priority)).fontSize(11).fontColor(this.priorityColor(todo.priority)).backgroundColor(this.priorityBg(todo.priority)).padding({left:8,right:8,top:3,bottom:3}).borderRadius(10)Text('🗑️').fontSize(18).onClick(()=>this.deleteTodo(todo.id))}.width('100%').padding({left:20,right:20,top:12,bottom:12}).backgroundColor(this.isDarkMode?'#1F2937':'#FFFFFF').alignItems(VerticalAlign.Center)Divider().color(this.isDarkMode?'#374151':'#F3F4F6').margin({left:20})},(todo:TodoItem_3hb0)=>todo.id.toString())}}.layoutWeight(1).backgroundColor(this.isDarkMode?'#111827':'#FAFAFA')}.layoutWeight(1)}.width('100%').height('100%').backgroundColor(this.isDarkMode?'#111827':'#FFFFFF')}}

写在最后

这个待办页的代码不花哨,但状态关系很完整。把@State、getter 和数组更新这几块吃透,再去写更复杂的表单或列表页,会顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询