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

HarmonyOs应用《日记本》开发第9篇 - @State 状态管理详解

状态管理是声明式 UI 框架的核心。在 ArkUI 中,@State是最基础的状态管理装饰器,它让变量变为"响应式"——当变量值改变时,UI 会自动更新。本篇将通过日记项目的实际代码,深入解析@State的工作原理和最佳实践。

@State 基本概念

什么是响应式状态

@Statediaries:DiaryItem[]=[];@StateisLoading:boolean=true;

@State装饰的变量具有以下特性:

  1. 响应式:值变化时,引用该变量的 UI 自动重新渲染
  2. 本地性:状态属于当前组件,不与其他组件共享
  3. 初始化:必须提供初始值

没有 @State 的世界

如果不用@State

// ❌ 不使用 @Statestruct Index{diaries:DiaryItem[]=[];// 普通变量isLoading:boolean=true;asyncloadData():Promise<void>{this.diaries=awaitdiaryStore.getAllDiaries();// 数据变了,但 UI 不会更新!// 用户看到的还是空列表或加载中状态}}

使用@State

// ✅ 使用 @Statestruct Index{@Statediaries:DiaryItem[]=[];// 响应式变量@StateisLoading:boolean=true;asyncloadData():Promise<void>{this.isLoading=true;// UI 自动更新为加载状态this.diaries=awaitdiaryStore.getAllDiaries();// UI 自动更新为列表this.isLoading=false;// UI 自动隐藏加载状态}}

日记项目中的 @State 使用

Index 页面的状态

@Entry@Componentstruct Index{@Statediaries:DiaryItem[]=[];// 日记列表数据@StateisLoading:boolean=true;// 加载状态aboutToAppear():void{this.loadData();}onPageShow():void{this.loadData();}asyncloadData():Promise<void>{this.isLoading=true;// → UI 切换到加载中try{this.diaries=awaitdiaryStore.getAllDiaries();// → UI 切换到列表}catch(e){console.error('loadData error: '+JSON.stringify(e));}this.isLoading=false;// → 隐藏加载动画}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.length===0){// isLoading 为 false 且列表为空时显示this.EmptyView()}else{// isLoading 为 false 且列表有数据时显示this.DiaryList()}}}}

状态变化驱动的 UI 切换

isLoading = true isLoading = false diaries = [] diaries = [item1, item2, ...] │ │ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ │ │ │ ┌──────────────┐ │ │ 加载中... │ ───────→ │ │ 日记卡片 1 │ │ │ ⟳ │ │ ├──────────────┤ │ │ │ │ │ 日记卡片 2 │ │ └──────────────┘ │ └──────────────┘ │ └──────────────────┘

DiaryEdit 页面的状态

@Entry@Componentstruct DiaryEdit{@Statetitle:string='';// 标题@Statecontent:string='';// 内容@Statedate:string='';// 日期@Statemood:string='calm';// 心情@Stateweather:string='sunny';// 天气@StatediaryId:string='';// 日记ID@StateisEdit:boolean=false;// 是否编辑模式@Statesaving:boolean=false;// 保存中状态@StateselectedDate:Date=newDate();// 日期选择器选中值}

状态与 UI 的绑定关系

// 标题输入 → @State titleTextInput({text:this.title,placeholder:'给今天起个标题吧...'}).onChange((value:string)=>{this.title=value;// 状态更新 → 输入框保持同步})// 心情选择 → @State moodForEach(MoodList,(item:MoodWeatherItem)=>{Column(){Text(item.emoji).fontSize(28)Text(item.label).fontColor(this.mood===item.key?'#FF6B6B':'#999')// mood 变化时,选中颜色自动更新}.backgroundColor(this.mood===item.key?'#FFF0F0':'#FFF')// mood 变化时,背景色自动更新.border(this.mood===item.key?{width:1.5,color:'#FF6B6B'}:{width:0,color:'#FFF'}).onClick(()=>{this.mood=item.key;// 点击切换心情 → UI 自动更新选中态})})// 保存按钮 → @State savingText('保存').opacity(this.saving?0.5:1)// 保存中时半透明

DiaryDetail 页面的状态

@Entry@Componentstruct DiaryDetail{@Statediary:DiaryItem|null=null;// 日记数据@Stateloading:boolean=true;// 加载状态@StateshowDeleteDialog:boolean=false;// 删除弹窗显示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.showDeleteDialog=false;// 关闭弹窗})Text('删除').onClick(()=>{this.showDeleteDialog=false;// 关闭弹窗this.deleteDiary();// 执行删除})}}

@State 的观察机制

基本类型观察

@StateisLoading:boolean=true;// 赋值触发 UI 更新this.isLoading=false;// ✅ 触发更新this.isLoading=true;// ✅ 触发更新

对象类型观察

@Statediary:DiaryItem|null=null;// 整体替换触发更新this.diary=newDiaryItem(...);// ✅ 触发更新// 属性修改的观察行为this.diary.title='新标题';// ⚠️ 取决于框架实现

数组类型观察

@Statediaries:DiaryItem[]=[];// 整体替换触发更新this.diaries=newArray;// ✅ 触发更新// 数组方法this.diaries.push(newItem);// ✅ 触发更新this.diaries.splice(0,1);// ✅ 触发更新this.diaries.filter(...);// ⚠️ 返回新数组,需要重新赋值

在日记项目中,loadData方法通过整体替换更新数组:

asyncloadData():Promise<void>{this.diaries=awaitdiaryStore.getAllDiaries();// 整体替换}

$$ 双向绑定语法

日记详情页中使用了$$语法:

.bindContentCover($$this.showDeleteDialog,this.DeleteDialog())

$$是双向绑定操作符,用于将@State变量与组件的内置状态双向同步:

  • showDeleteDialog变为true→ 弹窗显示
  • 弹窗内部关闭 →showDeleteDialog变为false

@State 的初始化

必须提供初始值

// ✅ 正确:有初始值@Statetitle:string='';@StateisLoading:boolean=true;@Statediary:DiaryItem|null=null;@Statediaries:DiaryItem[]=[];// ❌ 错误:缺少初始值@Statetitle:string;@StateisLoading:boolean;

合理的默认值

日记项目中的默认值选择很有讲究:

@Statemood:string='calm';// 默认平静心情@Stateweather:string='sunny';// 默认晴天@StateisEdit:boolean=false;// 默认新建模式@Statesaving:boolean=false;// 默认非保存状态@StateselectedDate:Date=newDate();// 默认今天

状态管理最佳实践

1. 最小化状态

// ✅ 好的做法:只保留必要的 @State@Statediary:DiaryItem|null=null;@Stateloading:boolean=true;@StateshowDeleteDialog:boolean=false;privatediaryId:string='';// 不驱动 UI,不用 @State

2. 状态与计算值分离

// 不要为能计算出来的值创建 @State@Statediaries:DiaryItem[]=[];// ✅ 计算值直接在 build 中计算if(this.diaries.length===0){this.EmptyView()}else{this.DiaryList()}

3. 状态更新与错误处理

asyncloadData():Promise<void>{this.isLoading=true;try{this.diaries=awaitdiaryStore.getAllDiaries();}catch(e){console.error('loadData error: '+JSON.stringify(e));// 错误时也更新状态,避免一直加载中}this.isLoading=false;// 无论成功失败都关闭加载状态}

4. 防抖与竞态

asyncsaveDiary():Promise<void>{if(this.saving)return;// 防止重复提交this.saving=true;constsuccess=awaitdiaryStore.saveDiary(diary);this.saving=false;if(success){setTimeout(()=>{router.back();},500);}}

小结

@State是 ArkUI 状态管理的基石,通过日记项目的实践,我们掌握了:

  1. 响应式原理:@State 变量变化自动触发 UI 更新
  2. 状态设计:只将驱动 UI 的变量标记为 @State
  3. 类型支持:基本类型、对象类型、数组类型
  4. 双向绑定$$语法用于组件内置状态同步
  5. 最佳实践:最小化状态、合理默认值、竞态处理
http://www.cnnetsun.cn/news/3660504.html

相关文章:

  • 如何用20个关键词高效吸收4小时技术分享:从信息过载到知识内化
  • TMS320C2x DSP加载指令深度解析:从LACL到LAR的汇编优化实践
  • Cortile工作区管理终极指南:多桌面高效切换技巧
  • HS2-HF Patch终极指南:彻底解决Honey Select 2兼容性问题的完整解决方案
  • 国外陶土板施工案例分析
  • 数字墓碑技术:二维码与云存储实现生命记忆数字化
  • CC2510Fx/CC2511Fx看门狗与USART模块:嵌入式系统可靠性与通信设计指南
  • qboot性能优化实战:从动态链接到固件裁剪的10个关键技巧
  • Genspark 6.0 SecondBrain:构建个性化AI记忆系统的技术实践
  • Linux容器文件系统隔离:pivot_root机制详解
  • AI数字人生成技术解析:从Stable Diffusion到东方美学控制
  • CC253x硬件安全模块实战:AES加密与真随机数生成器驱动详解
  • MEEP如何获得正确的透射率
  • 快速上手RT-DETR-L_wired_table_cell_det_safetensors:5分钟完成表格单元格检测实战
  • 如何3分钟内掌握Chrome全屏截图插件的终极指南:一键捕获完整网页的完整解决方案
  • DM37x调试接口时序解析:ETM、SDTI与JTAG的硬件设计实战
  • 【Linux系统编程】进程切换
  • 《Windows 11 从入门到精通》1.4.6:新的分屏模式
  • 5步实现精准视线追踪:eyetracker开源项目完整指南
  • 《Windows 11 从入门到精通》1.4.8:全新的音效体验详解
  • AI编程Agent技术演进与开发效率提升实践
  • NFS扩展属性问题解析与解决方案
  • MySQL日期时间格式转换实战与优化
  • RedKnot推理引擎:基于注意力头拆分的KV Cache优化技术解析
  • 物理信息神经网络与强化学习的融合应用实践
  • 3步搞定老旧Mac升级:OpenCore Legacy Patcher终极指南
  • 强化学习与组合优化在复杂决策中的应用
  • 为什么选择4-bit量化版?Nemotron-3-Embed-1B性能对比:BF16/8bit/4bit显存占用与速度测试
  • 剪映AI模板制作终极手册:含12套可商用Prompt模板库+37个动态占位符语法表(限前200名领取)
  • CircuitJS1 Desktop Mod:免费离线电路仿真软件的完整终极指南