一、为什么要折腾 MVVM?从面条代码的痛点说起
在传统的开发模式或早期的 FA 模型中,我们往往习惯于在 UI 组件的生命周期里直接写业务逻辑。例如在 aboutToAppear 方法里直接发起网络请求,并在回调中更新 @State 变量。
然而对于'会议随记 Pro'的录音页面来说,该页面功能复杂,包括显示波形图、实时计算时长、监听麦克风状态、计算会议成本及数据持久化等功能。如果将这些逻辑全部放在 UI 文件中,会导致文件迅速膨胀至难以维护。
采用 MVVM 模式可以有效解决这一问题。我们将代码拆分为 Model(模型层)、View(视图层)和 ViewModel(视图模型层)。Model 代表真实的数据结构;View 负责界面展示;ViewModel 则作为两者之间的桥梁,负责处理 UI 所需的数据状态并提供操作接口。这种单向数据流和状态驱动的开发方式有助于提升代码可读性和可测试性。
二、鸿蒙状态管理的三剑客 State、Prop 与 Link
HarmonyOS NEXT 提供了强大的状态管理机制,主要包括以下三种装饰器:
- @State:用于组件内部状态管理。被标记为
@State的变量发生变化时,使用它的 UI 会自动刷新。适用于如isLoading或当前录音时长等私有状态。 - @Prop:实现父子组件间的单向数据传递。父组件更改属性后子组件随之更新,但子组件无法反向修改父组件数据。适合用于通用展示型组件,如'项目颜色标签'。
- @Link:支持父子组件间的数据双向绑定。当子组件需要修改父组件持有的数据时使用,如'设置页'中的'时薪设置组件'。但应谨慎使用以避免破坏单向数据流。
此外还应注意 @Observed 和 @ObjectLink 的配合使用,以确保对嵌套对象属性变化的监听生效。
三、封装 BaseViewModel
为提高代码复用率和统一异常处理流程,我们封装了一个 BaseViewModel 基类。它包含如下核心能力:
// entry/src/main/ets/commons/base/BaseViewModel.ts
/**
* 所有 ViewModel 的基类
* 负责统一处理 Loading 状态、错误捕获和资源释放
*/
export class BaseViewModel {
/**
* 页面是否正在加载数据
* UI 层可以监听这个变量来显示/隐藏 Loading 组件
*/
isLoading: boolean = false;
/**
* 错误信息提示
* 当发生异常时,这里会被赋值,UI 层可以监听并弹出 Toast
*/
errorMessage: string = '';
/**
* 统一的异步任务执行器
* 自动管理 isLoading 的状态切换,并自动捕获异常
*
* @param task 需要执行的异步函数
* @param onError 可选的错误回调,如果需要特殊处理错误可传入
*/
async launch<T>(task: () => Promise<T>, onError?: ): <T | > {
{
. = ;
. = ;
result = ();
result;
} (error) {
.();
. = error. || ;
(onError) {
(error );
}
;
} {
. = ;
}
}
}


