状态管理是声明式 UI 框架的核心。在 ArkUI 中,@State是最基础的状态管理装饰器,它让变量变为"响应式"——当变量值改变时,UI 会自动更新。本篇将通过日记项目的实际代码,深入解析@State的工作原理和最佳实践。
@State 基本概念
什么是响应式状态
@Statediaries:DiaryItem[]=[];@StateisLoading:boolean=true;@State装饰的变量具有以下特性:
- 响应式:值变化时,引用该变量的 UI 自动重新渲染
- 本地性:状态属于当前组件,不与其他组件共享
- 初始化:必须提供初始值
没有 @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,不用 @State2. 状态与计算值分离
// 不要为能计算出来的值创建 @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 状态管理的基石,通过日记项目的实践,我们掌握了:
- 响应式原理:@State 变量变化自动触发 UI 更新
- 状态设计:只将驱动 UI 的变量标记为 @State
- 类型支持:基本类型、对象类型、数组类型
- 双向绑定:
$$语法用于组件内置状态同步 - 最佳实践:最小化状态、合理默认值、竞态处理