鸿蒙原生开发手记:徒步迹 - @State/@Prop/@Link 状态管理

📅 2026/7/14 2:30:51 👁️ 阅读次数 📝 编程学习
鸿蒙原生开发手记:徒步迹 - @State/@Prop/@Link 状态管理

掌握 ArkUI 装饰器体系,构建响应式 UI


一、前言

状态管理是声明式 UI 框架的核心。ArkUI 提供了一套装饰器体系,让开发者声明式地定义组件状态和依赖关系,框架自动追踪状态变化并更新 UI。

本文将详解 @State、@Prop、@Link 三个最基础也最重要的装饰器。


二、状态管理概览

ArkUI 装饰器按作用范围分为三层:

装饰器作用范围用途
@State组件内部组件私有状态
@Prop父→子父组件向子组件传数据
@Link父子双向父子组件双向同步
@Provide跨级传递祖先→后代(提供者)
@Consume跨级传递后代→祖先(消费者)
@Watch状态监听监听状态变化回调
@ObjectLink对象属性监听对象属性级变化

三、@State - 组件私有状态

3.1 基本用法

@Componentstruct StepCounter{@StatestepCount:number=0;// 状态变量@StateisRecording:boolean=false;@StaterouteName:string='';build(){Column(){Text(`步数:${this.stepCount}`)// 自动响应变化.fontSize(20);Button('加1').onClick(()=>{this.stepCount++;// 状态变化,UI 自动更新});Button(this.isRecording?'记录中':'开始记录').backgroundColor(this.isRecording?'#FF6F00':'#4CAF50').onClick(()=>{this.isRecording=!this.isRecording;});}}}

3.2 @State 的深层变化监听

@Componentstruct RouteDisplay{@Stateroute:HikingRoute={id:1,name:'香山徒步',distance:8.5,tags:['森林','山景'],};build(){Column(){Text(this.route.name).fontSize(18);// 修改对象属性 - @State 会自动监听深层变化Button('修改名称').onClick(()=>{this.route={...this.route,name:'新路线名',// 重新赋值触发更新};});}}}

注意:@State 监听的是赋值操作,修改对象属性时需要重新赋值才会触发更新。对于数组,需要使用新数组替换。


四、@Prop - 父传子

@Prop 用于父组件向子组件传递数据,是单向数据流:

// 子组件 - 接收父组件传入的数据@Componentstruct RouteCard{@ProprouteName:string='';// 从父组件接收@Propdistance:number=0;@Propdifficulty:string='简单';build(){Column(){Text(this.routeName).fontSize(16).fontWeight(FontWeight.Bold);Text(`距离:${this.distance}km`).fontSize(14).fontColor('#666');Text(`难度:${this.difficulty}`).fontSize(12).fontColor(this.getDifficultyColor());}.padding(12).backgroundColor('#FFF').borderRadius(8);}getDifficultyColor():ResourceColor{if(this.difficulty==='简单')return'#4CAF50';if(this.difficulty==='中等')return'#FF9800';return'#F44336';}}// 父组件 - 传入数据@Entry@Componentstruct RouteListPage{build(){Column(){RouteCard({routeName:'香山徒步',distance:8.5,difficulty:'中等',});}.padding(16);}}

@Prop 的限制

  • 子组件不能修改 @Prop 的值(只读)
  • 父组件数据变化时,@Prop 自动同步更新
  • @Prop 是深拷贝,不会影响父组件原始数据

五、@Link - 父子双向同步

@Link 实现父子组件间的数据双向同步:

// 子组件@Componentstruct RecordingControl{@Link@Watch('onStatusChanged')status:string;// 双向绑定@Linkduration:number;onStatusChanged():void{console.log(`状态变更为:${this.status}`);}build(){Column(){// 子组件修改 @Link 变量 → 父组件同步更新Button(this.status==='recording'?'暂停':'继续').onClick(()=>{this.status=this.status==='recording'?'paused':'recording';});Text(`时长:${this.duration}s`);}}}// 父组件@Entry@Componentstruct TrackingPage{@StaterecordingStatus:string='idle';@StaterecordingDuration:number=0;build(){Column(){Text(`状态:${this.recordingStatus}`).fontSize(16);// 使用 $ 前缀传递 @Link 引用RecordingControl({status:$recordingStatus,duration:$recordingDuration,});}}}

$ 语法

使用$变量名将 @State 变量的引用传递给 @Link:

// 父组件传递ChildComponent({propVar:this.stateVar,// @Prop: 传值linkVar:$stateVar,// @Link: 传引用})

六、实战:徒步迹状态管理

结合"徒步迹"的轨迹记录页面,实际演示状态管理的使用:

// TrackingPage.ets@Entry@Componentstruct TrackingPage{@StateisRecording:boolean=false;@StateisPaused:boolean=false;@Stateduration:number=0;@Statedistance:number=0;privatetimerId:number=-1;build(){Column(){// 统计数据区域Row(){StatItem({label:'时长',value:this.formatTime(this.duration)});StatItem({label:'距离',value:`${this.distance.toFixed(2)}`});StatItem({label:'配速',value:this.calcPace()});}.width('100%').padding(16);// 控制按钮区域Row(){if(!this.isRecording){Button('开始徒步').width(180).height(56).backgroundColor('#4CAF50').borderRadius(28).onClick(()=>this.startRecording());}else{Button(this.isPaused?'继续':'暂停').width(72).height(72).backgroundColor(this.isPaused?'#4CAF50':'#FF9800').borderRadius(36).onClick(()=>{if(this.isPaused)this.resumeRecording();elsethis.pauseRecording();});Button('停止').width(72).height(72).backgroundColor('#F44336').borderRadius(36).margin({left:40}).onClick(()=>this.stopRecording());}}.padding({bottom:40});}.width('100%').height('100%');}startRecording():void{this.isRecording=true;this.timerId=setInterval(()=>{this.duration++;this.distance+=0.005;},1000);}pauseRecording():void{this.isPaused=true;clearInterval(this.timerId);}resumeRecording():void{this.isPaused=false;this.startTimer();}stopRecording():void{this.isRecording=false;this.isPaused=false;clearInterval(this.timerId);// 重置数据this.duration=0;this.distance=0;}startTimer():void{this.timerId=setInterval(()=>{this.duration++;this.distance+=0.005;},1000);}formatTime(seconds:number):string{constm=Math.floor(seconds/60);consts=seconds%60;return`${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;}calcPace():string{if(this.distance<=0)return'0\'00"';constpace=this.duration/this.distance;return`${Math.floor(pace/60)}'${Math.floor(pace%60).toString().padStart(2,'0')}"`;}}@Componentstruct StatItem{@Proplabel:string='';@Propvalue:string='';build(){Column(){Text(this.value).fontSize(24).fontWeight(FontWeight.Bold);Text(this.label).fontSize(13).fontColor('#666');}.alignItems(HorizontalAlign.Center);}}

七、@State 最佳实践

  1. 最小化状态:只保留需要 UI 响应的变量
  2. 避免冗余:能从其他状态推导的值不要单独声明
  3. 类型明确:始终为 @State 变量提供初始值
  4. 不可变性:尽量使用只读接口
// 好 - 状态最小化@Statedistance:number=0;// 只需存储基础数据// 配速由 duration 和 distance 计算得出,不需要额外状态// 不好 - 状态冗余@Statedistance:number=0;@Statepace:string='0\'00"';// 可以从 distance/duration 推导

八、总结

本文详细介绍了 ArkUI 状态管理的三个核心装饰器:@State(组件私有)、@Prop(父传子)、@Link(双向同步)。理解它们的区别和适用场景,是构建正确响应式 UI 的关键。

下一篇文章将学习跨组件通信的 @Provide/@Consume 装饰器。


下一篇预告:鸿蒙原生开发手记:徒步迹 - @Provide/@Consume 跨组件通信