HarmonyOs应用《日记本》开发第9篇 - @State 状态管理详解
状态管理是声明式 UI 框架的核心。在 ArkUI 中State是最基础的状态管理装饰器它让变量变为响应式——当变量值改变时UI 会自动更新。本篇将通过日记项目的实际代码深入解析State的工作原理和最佳实践。State 基本概念什么是响应式状态Statediaries:DiaryItem[][];StateisLoading:booleantrue;State装饰的变量具有以下特性响应式值变化时引用该变量的 UI 自动重新渲染本地性状态属于当前组件不与其他组件共享初始化必须提供初始值没有 State 的世界如果不用State// ❌ 不使用 Statestruct Index{diaries:DiaryItem[][];// 普通变量isLoading:booleantrue;asyncloadData():Promisevoid{this.diariesawaitdiaryStore.getAllDiaries();// 数据变了但 UI 不会更新// 用户看到的还是空列表或加载中状态}}使用State// ✅ 使用 Statestruct Index{Statediaries:DiaryItem[][];// 响应式变量StateisLoading:booleantrue;asyncloadData():Promisevoid{this.isLoadingtrue;// UI 自动更新为加载状态this.diariesawaitdiaryStore.getAllDiaries();// UI 自动更新为列表this.isLoadingfalse;// UI 自动隐藏加载状态}}日记项目中的 State 使用Index 页面的状态EntryComponentstruct Index{Statediaries:DiaryItem[][];// 日记列表数据StateisLoading:booleantrue;// 加载状态aboutToAppear():void{this.loadData();}onPageShow():void{this.loadData();}asyncloadData():Promisevoid{this.isLoadingtrue;// → UI 切换到加载中try{this.diariesawaitdiaryStore.getAllDiaries();// → UI 切换到列表}catch(e){console.error(loadData error: JSON.stringify(e));}this.isLoadingfalse;// → 隐藏加载动画}build():void{Column(){this.HeaderBar()if(this.isLoading){// isLoading 为 true 时显示Column(){LoadingProgress().width(40).height(40).color(#FF6B6B)Text(加载中...).fontSize(14).fontColor(#999)}}elseif(this.diaries.length0){// isLoading 为 false 且列表为空时显示this.EmptyView()}else{// isLoading 为 false 且列表有数据时显示this.DiaryList()}}}}状态变化驱动的 UI 切换isLoading true isLoading false diaries [] diaries [item1, item2, ...] │ │ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ │ │ │ ┌──────────────┐ │ │ 加载中... │ ───────→ │ │ 日记卡片 1 │ │ │ ⟳ │ │ ├──────────────┤ │ │ │ │ │ 日记卡片 2 │ │ └──────────────┘ │ └──────────────┘ │ └──────────────────┘DiaryEdit 页面的状态EntryComponentstruct DiaryEdit{Statetitle:string;// 标题Statecontent:string;// 内容Statedate:string;// 日期Statemood:stringcalm;// 心情Stateweather:stringsunny;// 天气StatediaryId:string;// 日记IDStateisEdit:booleanfalse;// 是否编辑模式Statesaving:booleanfalse;// 保存中状态StateselectedDate:DatenewDate();// 日期选择器选中值}状态与 UI 的绑定关系// 标题输入 → State titleTextInput({text:this.title,placeholder:给今天起个标题吧...}).onChange((value:string){this.titlevalue;// 状态更新 → 输入框保持同步})// 心情选择 → State moodForEach(MoodList,(item:MoodWeatherItem){Column(){Text(item.emoji).fontSize(28)Text(item.label).fontColor(this.mooditem.key?#FF6B6B:#999)// mood 变化时选中颜色自动更新}.backgroundColor(this.mooditem.key?#FFF0F0:#FFF)// mood 变化时背景色自动更新.border(this.mooditem.key?{width:1.5,color:#FF6B6B}:{width:0,color:#FFF}).onClick((){this.mooditem.key;// 点击切换心情 → UI 自动更新选中态})})// 保存按钮 → State savingText(保存).opacity(this.saving?0.5:1)// 保存中时半透明DiaryDetail 页面的状态EntryComponentstruct DiaryDetail{Statediary:DiaryItem|nullnull;// 日记数据Stateloading:booleantrue;// 加载状态StateshowDeleteDialog:booleanfalse;// 删除弹窗显示privatediaryId:string;// 普通变量不驱动 UI}注意diaryId没有用State装饰因为它不直接驱动 UI 渲染只是内部使用的标识。删除弹窗的状态控制build():void{Column(){this.NavBar()if(this.loading){// 加载中}elseif(this.diary){this.DetailContent()}else{// 日记不存在}}// 弹窗绑定到 showDeleteDialog 状态.bindContentCover($$this.showDeleteDialog,this.DeleteDialog())}BuilderDeleteDialog():void{Column(){// ...Text(取消).onClick((){this.showDeleteDialogfalse;// 关闭弹窗})Text(删除).onClick((){this.showDeleteDialogfalse;// 关闭弹窗this.deleteDiary();// 执行删除})}}State 的观察机制基本类型观察StateisLoading:booleantrue;// 赋值触发 UI 更新this.isLoadingfalse;// ✅ 触发更新this.isLoadingtrue;// ✅ 触发更新对象类型观察Statediary:DiaryItem|nullnull;// 整体替换触发更新this.diarynewDiaryItem(...);// ✅ 触发更新// 属性修改的观察行为this.diary.title新标题;// ⚠️ 取决于框架实现数组类型观察Statediaries:DiaryItem[][];// 整体替换触发更新this.diariesnewArray;// ✅ 触发更新// 数组方法this.diaries.push(newItem);// ✅ 触发更新this.diaries.splice(0,1);// ✅ 触发更新this.diaries.filter(...);// ⚠️ 返回新数组需要重新赋值在日记项目中loadData方法通过整体替换更新数组asyncloadData():Promisevoid{this.diariesawaitdiaryStore.getAllDiaries();// 整体替换}$$ 双向绑定语法日记详情页中使用了$$语法.bindContentCover($$this.showDeleteDialog,this.DeleteDialog())$$是双向绑定操作符用于将State变量与组件的内置状态双向同步showDeleteDialog变为true→ 弹窗显示弹窗内部关闭 →showDeleteDialog变为falseState 的初始化必须提供初始值// ✅ 正确有初始值Statetitle:string;StateisLoading:booleantrue;Statediary:DiaryItem|nullnull;Statediaries:DiaryItem[][];// ❌ 错误缺少初始值Statetitle:string;StateisLoading:boolean;合理的默认值日记项目中的默认值选择很有讲究Statemood:stringcalm;// 默认平静心情Stateweather:stringsunny;// 默认晴天StateisEdit:booleanfalse;// 默认新建模式Statesaving:booleanfalse;// 默认非保存状态StateselectedDate:DatenewDate();// 默认今天状态管理最佳实践1. 最小化状态// ✅ 好的做法只保留必要的 StateStatediary:DiaryItem|nullnull;Stateloading:booleantrue;StateshowDeleteDialog:booleanfalse;privatediaryId:string;// 不驱动 UI不用 State2. 状态与计算值分离// 不要为能计算出来的值创建 StateStatediaries:DiaryItem[][];// ✅ 计算值直接在 build 中计算if(this.diaries.length0){this.EmptyView()}else{this.DiaryList()}3. 状态更新与错误处理asyncloadData():Promisevoid{this.isLoadingtrue;try{this.diariesawaitdiaryStore.getAllDiaries();}catch(e){console.error(loadData error: JSON.stringify(e));// 错误时也更新状态避免一直加载中}this.isLoadingfalse;// 无论成功失败都关闭加载状态}4. 防抖与竞态asyncsaveDiary():Promisevoid{if(this.saving)return;// 防止重复提交this.savingtrue;constsuccessawaitdiaryStore.saveDiary(diary);this.savingfalse;if(success){setTimeout((){router.back();},500);}}小结State是 ArkUI 状态管理的基石通过日记项目的实践我们掌握了响应式原理State 变量变化自动触发 UI 更新状态设计只将驱动 UI 的变量标记为 State类型支持基本类型、对象类型、数组类型双向绑定$$语法用于组件内置状态同步最佳实践最小化状态、合理默认值、竞态处理