HarmonyOS 多设备短视频开发 :23 — @Monitor 与 @Computed:计算与监听
23 — @Monitor 与 @Computed:计算与监听
一、引言
在状态管理 V2 中,@Trace 解决了"数据可观察"的问题,但"数据变化后要做什么"仍需要一种显式机制:@Monitor 监听 @Trace 属性变化并触发业务逻辑,@Computed 提供带缓存的派生计算属性。本项目播放器组件 features/multishortvideoadaptivevideo/src/main/ets/view/AdaptiveAVPlayer.ets 用三个 @Monitor 串起了"滑动切换视频、窗口尺寸变化、拖动进度条"三条关键联动链路,是理解 V2 监听机制的绝佳样本。本文结合该文件逐一拆解 @Monitor 的用法与 @Computed 的适用场景。

二、@Monitor 基本用法
@Monitor 装饰方法,监听一个或多个 @Trace(或 @Local/@Param 等)属性,属性变化时自动调用。AdaptiveAVPlayer.ets 中第一个 @Monitor 处理"当前播放下标变化":
// features/multishortvideoadaptivevideo/src/main/ets/view/AdaptiveAVPlayer.ets(真实代码)
@Monitor('currentIndex')
onIndexChange() {
if (this.currentIndex !== this.index) {
// 非当前项:暂停播放,节省资源
this.avPlayer?.pause().catch(() => {
Logger.error(TAG, 'Pause AVPlayer failed.');
})
} else {
this.changePortraitVideo();
if (this.hasPrepared) {
if (this.canPlay()) {
this.avPlayer?.seek(0, media.SeekMode.SEEK_CLOSEST);
this.avPlayer?.play().catch(() => {
Logger.error(TAG, 'Play AVPlayer failed');
})
}
} else {
this.initAVPlayer()
}
}
}
当前后滑动手势切换视频时,父组件 AdaptiveVideo 在 Swiper 的 onAnimationStart 中更新 this.curIndex 并传给播放器的 @Param currentIndex,@Monitor 随即被触发:非当前页暂停、当前页回到起点并播放或初始化。业务逻辑与 UI 刷新解耦,且天然携带"哪个属性变了"的上下文。
三、@Monitor 多属性监听与完整方法签名
@Monitor 支持一次监听多个属性,AdaptiveAVPlayer.ets 中窗口尺寸联动即如此:
// 真实代码:窗口宽高任一变化都重算视频显示区域
@Monitor('windowSizeWidth', 'windowSizeHeight')
onWindowSizeChange() {
this.changePortraitVideo();
}
@Monitor 方法还可接收 IMonitorChange 参数,获取变化前后的值:
@Monitor('seekToTime')
onSeekToTimeChange(change: IMonitorChange) {
const oldValue = change.value?.['seekToTime'] as number; // 变化前
const newValue = change.changeValue?.['seekToTime'] as number; // 变化后
if (this.seekToTime >= 0 && this.avPlayer && this.canSeek()) {
this.avPlayer.seek(Math.floor(this.seekToTime), media.SeekMode.SEEK_CLOSEST);
}
}
change.value 记录属性变化前的值,change.changeValue 记录变化后的值,可用于差值计算、埋点上报或"恢复现场"(如拖拽进度时记住原进度,取消拖拽可回退)。IMonitorChange 的结构可归纳为:
| 字段 | 含义 |
|---|---|
| value | 变化前各监听属性的值集合 |
| changeValue | 变化后各监听属性的值集合 |
| keys | 本次实际变化的属性名数组 |
keys 在多属性监听时尤其有用:即使同时监听宽高,也能通过 keys 判断这次是宽度变了还是高度变了,从而决定是否触发重排。项目中的 @Monitor('seekToTime') 正是拖动进度条的核心链路:用户在进度条上拖动 → 父组件 AdaptiveVideo 通过 updateSeekByPosition 计算目标时间并更新 seekToTime → @Monitor 触发 seek 跳转,配合 isSeeking 标志位区分"拖拽预览"与"播放推进",从而实现了"拖到哪播到哪"的实时预览。
四、@Computed:带缓存的派生属性
@Computed 装饰 getter 方法,基于 @Trace 属性派生出新值,且只在依赖项变化时重新计算,结果自动缓存。典型场景是把播放进度换算为进度条百分比或时间文本:
// 扩展示例:基于项目 currentTime/duration 的派生计算(项目风格)
@ComponentV2
struct AdaptiveVideo {
@Consumer('currentTime') currentTime: number = 0;
@Consumer('duration') duration: number = 0;
@Computed
get progressPercent(): number {
if (this.duration <= 0) {
return 0;
}
return Math.min(100, (this.currentTime / this.duration) * 100);
}
@Computed
get timeText(): string {
const total = Math.floor(this.duration / 1000);
const cur = Math.floor(this.currentTime / 1000);
const format = (sec: number): string =>
`${Math.floor(sec / 60)}:${String(sec % 60).padStart(2, '0')}`;
return `${format(cur)} / ${format(total)}`;
}
}
与每次渲染都执行计算的普通 getter 不同,@Computed 只在依赖的 @Trace 属性变化时重算,timeUpdate 每 500ms 触发一次 currentTime 更新时,只有真正变化才触发重算,避免高频渲染下的无谓计算。派生逻辑集中收口在组件内,也提升了可读性。
需要说明的是,@Computed 只读不可写,若需要"百分比 → 目标时间"的反向换算(拖动进度条时把手指位置换算为 seekToTime),仍应写在普通方法中(项目 AdaptiveVideo.ets 的 updateSeekByPosition 即为此类),单向数据流下计算与赋值各司其职。
五、@Monitor 与 @Watch 对比
| 维度 | V1 @Watch | V2 @Monitor |
|---|---|---|
| 获取变化前后值 | 不支持 | 支持(IMonitorChange) |
| 多属性监听 | 单属性 | 支持,可监听多个 |
| 依赖的观察体系 | @State 等 V1 | @Trace 等 V2 属性 |
| 触发时机 | 属性变化后 | 属性变化后(异步批量) |
| 应用场景 | 简单回调 | 联动播放、窗口重算、持久化 |
@Monitor 还具备去重与批量能力:同一帧内多次修改被观察属性,方法只会执行一次,天然适合合并窗口 resize 这类高频事件。AdaptiveAVPlayer.ets 中窗口尺寸监听只做一次重算,正是受益于此。
此外,@Monitor 对 @Local、@Param、@Consumer 等 V2 装饰器属性同样生效:AdaptiveAVPlayer.ets 中 currentIndex 是 @Param(父组件传入)、seekToTime 是 @Param、windowSizeWidth/windowSizeHeight 是 @Consumer,三种来源的属性变化都能触发对应监听,说明 @Monitor 与"状态从哪来"无关,只关心"属性值是否变化"。
六、使用注意事项
- @Monitor 不能修改被监听的属性本身,否则会形成递归触发,应通过其他状态或事件回传。
- 方法体内优先读取变化后的最新值,如需历史值用 IMonitorChange 的 change.value。
- @Computed 依赖的必须是 @Trace/@Local 等可观察属性,普通成员变量变化不会触发重算。
- 播放器回调(onStateChange、onTimeUpdateFunction)中直接赋值 @Consumer 属性,配合 @Monitor 联动 UI,注意不要在 @Monitor 中做耗时操作。
- 组件销毁时 @Monitor 自动解绑,无需手动注销,但播放器等原生资源仍需在 aboutToDisappear 中显式释放。
七、总结与最佳实践
- 用 @Monitor 把"状态变化"与"业务副作用"显式关联:切视频暂停/起播、窗口变化重排、进度跳转,都收敛为一条清晰的联动链路。
- 多属性联动的场景(宽高、坐标)一次 @Monitor 监听多个属性,利用批量去重天然抗抖。
- 需要变化前后值(拖拽回退、差值统计)时使用 IMonitorChange 参数。
- 派生展示值(百分比、时间文本、格式化文案)用 @Computed,享受缓存的同时保持代码整洁。
- 坚持单向数据流:@Monitor 只做副作用,UI 刷新交给 @Trace 自身完成,二者职责不重叠。
更多推荐




所有评论(0)