HarmonyOS 多设备短视频开发 :21 — 状态管理 V1 装饰器体系
2026/8/31 9:03:51 网站建设 项目流程

21 — 状态管理 V1 装饰器体系

一、引言

ArkUI 声明式开发的核心是"状态驱动 UI":状态变化自动触发依赖该状态的组件刷新。在 HarmonyOS 5.0 之前的版本,这套能力由状态管理 V1 装饰器承担,包括 @State、@Prop、@Link、@Provide、@Consume、@ObjectLink、@Watch 等。本项目(multi-short-video)已全面升级为 V2 装饰器体系(见文章 22),但掌握 V1 仍然是必要的:一方面,存量工程与历史三方库大量使用 V1;另一方面,V1 的"单向数据流 + 就近管理"思想是理解 V2 演进的地基。本文结合项目场景,逐一定位 V1 各装饰器的职责与传递规则,并指出常见状态不同步问题的根因。

二、@State:组件内状态的最小单元

@State 装饰的变量由组件自身管理,其变化会触发该组件 build 函数的重新执行。以视频页features/multishortvideoadaptivevideo/src/main/ets/view/AdaptiveVideo.ets为例,页面需要跟踪当前视频下标、评论弹层开关、播放状态等;该项目文件使用 V2 语法,等价的 V1 写法如下:

// 对照示例:AdaptiveVideo.ets 相关状态的 V1 等价写法@Entry@Componentstruct AdaptiveVideo{@StatecurIndex:number=0;@StateshowComment:boolean=false;@StatecurrentState:string='idle';....onAnimationStart((index:number,targetIndex:number)=>{this.curIndex=targetIndex;// 直接赋值即可触发刷新this.currentTime=0;})}

要点:@State 只作用于组件内部,且初始化只能使用本地初始值,不能依赖父组件传入的参数。在 Swiper 切换动画回调中直接修改this.curIndex,V1 会自动最小化刷新绑定该变量的 UI 节点。

三、@Prop 与 @Link:父子组件的单向与双向同步

父子组件传参是状态管理最频繁的场景。V1 中:

  • @Prop 建立单向同步:父组件数据变化 → 子组件 @Prop 同步更新;子组件内部修改 @Prop 不会回传父组件。适合"只读展示"型参数。
  • @Link 建立双向同步:父子共享同一数据源,任一侧修改都会同步到另一侧。适合"需要子组件回写"的参数。

以播放器组件AdaptiveAVPlayer.ets为例,父组件 AdaptiveVideo 需要把当前播放下标、视频源、拖拽目标时间传给子组件,V1 等价写法:

// 对照示例:AdaptiveAVPlayer.ets 入参的 V1 等价写法@Componentexportstruct AdaptiveAVPlayer{@PropcurrentIndex:number=-1;// 父传子,单向同步@Propindex:number=0;@PropcurrentSource:string='';@PropseekToTime:number=-1;// 拖拽进度由父组件写入...@Linkduration:number;// 与父组件共享时长,双向同步}

需要注意:@Prop 的深拷贝特性决定了它对嵌套对象只同步一层;@Link 则要求变量类型一致且不能就地初始化(由父组件传入)。项目中将"是否正在拖拽、目标时间"等状态统一放在父组件 AdaptiveVideo,通过 @Prop 单向下发,避免了子组件随意回写导致的职责混乱。

四、@Provide 与 @Consume:跨层级共享

当状态需要在多层组件间传递时,逐层 @Prop 会形成"属性钻透"。V1 提供 @Provide/@Consume 实现跨层级共享:祖先组件 @Provide 提供数据,任意层级后代 @Consume 消费同名数据,中间层无需感知。本项目首页products/default/src/main/ets/view/Index.ets将深色模式、路由栈、侧栏开关声明在根部,供视频页、评论页等多层组件消费,V1 等价写法:

// 对照示例:Index.ets 根组件的 V1 等价写法@Entry@Componentstruct Index{@Provide('isDark')isDark:boolean=false;@Provide('pathStack')pathStack:NavPathStack=newNavPathStack();@Provide('showSideComment')showSideComment:boolean=false;@Provide('showSideIndividual')showSideIndividual:boolean=false;...}

消费侧(如 AdaptiveVideo.ets 的等价写法):

@Consume('showSideComment')showSideComment:boolean;@Consume('pathStack')pathStack:NavPathStack;

当视频页切换页签需要收起侧栏时,直接修改this.showSideComment,根组件与所有消费方同步刷新。@Provide 默认向全部后代可见,若只想对直接子组件可见,可配合 @Provide 的别名机制控制作用域。

五、@ObjectLink 与 @Watch:对象级观察与监听

  • @ObjectLink 装饰 class 类型(需配合 @Observed 类装饰器),对对象内部属性变化做深度观察,常用于数组项或嵌套对象传入子组件的场景。
  • @Watch 为状态变化添加回调,在数据变化后触发业务逻辑(如保存、上报),但 @Watch 不提供变化前后的值。

以评论数据features/multishortvideocomment/src/main/ets/model/CommentDataModel.ets为例,若使用 V1 体系,评论对象可声明为 @Observed class,列表项子组件用 @ObjectLink 接收,点赞数等属性就地修改即可精准刷新单条评论,而不是整表重绘:

@ObservedexportclassCommentDataModel{publiclikes:number=0;publiccontent:string='';...}// 子组件内@Componentexportstruct CommentItem{@ObjectLinkitem:CommentDataModel;// 深度观察对象属性@Watch('onLikesChange')@Proplikes:number=0;onLikesChange(){// 点赞数变化后的业务逻辑}}

V1 装饰器能力汇总如下表:

装饰器同步方向支持类型典型场景
@State组件内基本类型、对象、数组页面局部状态
@Prop父 → 子(单向)基本类型、对象一层只读入参
@Link父子双向与父类型一致需要回写的共享值
@Provide/@Consume祖先 → 后代任意跨多层共享
@ObjectLink父 → 子(深度)@Observed 对象数组项、嵌套对象
@Watch监听回调任意 @State 等变化后触发逻辑

六、V1 常见状态不同步问题排查

实践中状态不同步多源于以下几类:

  1. 对象内部属性不刷新:普通 class 对象属性变化不会触发 UI 更新,必须配合 @Observed + @ObjectLink,或整体替换对象引用。
  2. @Prop 修改不回传:误以为 @Prop 是双向的,在子组件修改后父组件无感知。需要双向时改用 @Link。
  3. @Provide/@Consume 名称不匹配:两侧 key 必须完全一致(含别名写法),否则编译期不报错但运行时不生效。
  4. ForEach/Repeat 渲染键不稳定:用 JSON.stringify(item) 生成键(项目在.key((item: AvDataSourceModel) => JSON.stringify(item))中采用),避免索引键导致的状态错位。
  5. 在非 UI 线程/回调中修改状态:某些异步回调(如 AVPlayer 的 stateChange)中直接赋值可能错过刷新时机,应回到 UI 上下文再赋值。

七、总结与最佳实践

  1. 状态就近声明:只被单个组件使用的状态用 @State,避免无谓提升。
  2. 参数只读优先 @Prop,需要回写才用 @Link;跨层共享优先 @Provide/@Consume,减少逐层透传。
  3. 对象级数据用 @Observed + @ObjectLink,保证细粒度刷新。
  4. 需要感知状态变化执行副作用(保存、埋点、联动播放器)时使用 @Watch。
  5. V1 的"就近管理"思想是 V2 的基石;新项目建议直接采用 V2(下一章详解),存量工程按需迁移。

注:文中 V1 示例均为项目既有场景的等价对照写法,multi-short-video 实际代码已采用 V2 装饰器,迁移思路见文章 22。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询