HarmonyOS7 待办状态管理页实战:用 @State 把页面写活 文章目录先从“谁会变化”开始看列表更新要返回新数组getter 让页面少写临时变量State 适合页面内部不适合所有场景深色模式也是状态驱动的例子完整代码写在最后State是 ArkUI 里最常见的状态装饰器也最容易被低估。很多人第一次用它只会写一个计数器但真正做页面时它要同时撑住输入框、列表、筛选、进度、主题切换这些交互。这个待办页就很适合练手功能不复杂但状态变化非常密集。先从“谁会变化”开始看页面里会变化的东西很多计数器数字、输入框内容、深色模式、待办列表、是否只看未完成。它们都属于当前页面内部状态所以用State很合适。TodoItem_3hb0只描述一条任务id、文本、完成状态和优先级。数据模型越简单后面的增删改查越不容易乱。列表更新要返回新数组待办列表最典型的操作是切换完成状态。这里没有直接改原数组里的对象而是用map()生成一个新数组再赋值回this.todos。toggleTodo(id:number){this.todosthis.todos.map((t:TodoItem_3hb0){if(t.idid){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}EntryComponentstruct StateTodoPage{// State 基础用法触发组件重新渲染Statecount:number0StateinputText:stringStateisDarkMode:booleanfalseStatetodos: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:booleanfalsegetfilteredTodos():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.todosthis.todos.map((t:TodoItem_3hb0){if(t.idid){return{id:t.id,text:t.text,done:!t.done,priority:t.priority}}returnt})}addTodo(){if(this.inputText.trim().length0)returnconstnewTodo:TodoItem_3hb0{id:Date.now(),text:this.inputText,done:false,priority:2}this.todosthis.todos.concat([newTodo])this.inputText}deleteTodo(id:number){this.todosthis.todos.filter((t:TodoItem_3hb0)t.id!id)}priorityLabel(p:number):string{if(p1)return高if(p2)return中return低}priorityColor(p:number):string{if(p1)return#EF4444if(p2)return#D97706return#9CA3AF}priorityBg(p:number):string{if(p1)return#FEE2E2if(p2)return#FEF3C7return#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.count0?#2563EB:this.count0?#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.count0})}.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.inputTextval}).onSubmit(()this.addTodo())Button(添加).fontSize(13).borderRadius(8).fontColor(Color.White).backgroundColor(this.inputText.length0?#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 和数组更新这几块吃透再去写更复杂的表单或列表页会顺很多。