HarmonyOS鸿蒙开发 MVVM模式及状态管理

HarmonyOS鸿蒙开发 MVVM模式及状态管理

最近在学习鸿蒙开发过程中,阅读了官方文档,尝试写一写代码。想起来了之前写flutter的代码结构,采用MVVM模式。 这里学习写一下HarmonyOS鸿蒙开发MVVM模式及代码结构。

效果预览

一、MVVM

MVVM架构的核心思想是将应用程序划分为三个主要部分:Model、View和ViewModel。这三者之间的交互和数据流如下图所示:

  • 1.Model: Model层负责管理应用的数据 业务逻辑,比如可以通过它来访问数据库、网络请求、本地文件等获取对应的数据,更新数据等。

  • 2.View:view层负责展示用户界面,处理用户的交互操作等,比如它可以是页面的struct,自定义的builder等等

  • 3.ViewModel:ViewModel层作为Model与View的桥接层,可以通过它向Model层获取数据、转化数据用户View展示及交互操作的相关数据,ViewModel也将处理用户交互的事件,处理交互事件逻辑转化成对Model层的数据进行更新等

在HarmonyOS鸿蒙开发中,我这里使用的是V2所属装饰器@ObservedV2、@Trace等,当然涉及到的界面也是@ComponentV2。

二、页面的状态

我这里将一个页面常见的状态分为idle(默认显示)、busy(加载中)、empty(无数据)、error(加载出现错误)。 在页面加载过程中,首先出现加载中,如果数据加载失败,则显示错误的缺省页面;如果无数据,则显示无数据的缺省页面;如果有数据则回到idle显示页面。

所以这里定义枚举CompState

arduino 复制代码
/// 定义组件状态类型,一般页面加载有加载中,无数据,加载错误等状态
/// 定义错误类型,一般页面加载有网络错误,其他错误类型
export enum CompState {
  idle, // 默认
  busy, // 加载中
  empty, // 无数据
  error // 加载出现错误
}

若是加载失败的情况下,可能还需要区分失败的原因,如网络连接错误、服务器500异常、404 Notfound等错误。这就需要定义错误类型及错误的信息了

错误类型CompErrorType

arduino 复制代码
export enum CompErrorType {
  default, // 一般错误
  network, // 网络错误
  unAuth, // 未授权错误
}

错误的信息类CompStateError

kotlin 复制代码
/// 错误的接口数据
export interface CompStateErrorConfig {
  // 错误类型
  errorType?: CompErrorType
  // message
  message?: string
  // 错误message,一般用于toast提醒
  errorMessage?: string
}

/// 错误类,使用class,需要在该类中做一些常用的方法,方便处理
@ObservedV2
export default class CompStateError {
  // 错误类型
  @Trace errorType?: CompErrorType
  // message
  @Trace message?: string
  // 错误message,一般用于toast提醒
  @Trace errorMessage?: string

  // 构造函数
  constructor(config: CompStateErrorConfig) {
    this.errorType = config.errorType;
    this.message = config.message;
    this.errorMessage = config.errorMessage;
  }

  /// 方便调用错误类型
  get isDefaultError(): boolean {
    return CompErrorType.default === this.errorType;
  }

  get isNetworkError(): boolean {
    return CompErrorType.network === this.errorType;
  }

  get isUnAuthError(): boolean {
    return CompErrorType.unAuth === this.errorType;
  }

  // 输入日志,这里只是方便调用输出控制台log
  toString(): string {
    return "CompStateError{errorType: " + this.errorType + ", message: " + this.message + ", errorMessage: " +
    this.errorMessage;
  }
}

确定了这些,可以为应用创建对应的Model层、ViewModel层、View层等结构了。

三、MVVM项目代码

3.1、ViewModel层的BaseCompVM

ViewModel层:负责管理UI及业务逻辑,绑定数据,监听View的交互事件。 这里ViewModel中确定几个属性

  • 1.页面状态viewState:定义当前页面的状态,CompState枚举,确定页面是否在加载中、还是请求出现错误等
  • 2.是否destory:定义是否销毁,防止页面销毁后,异步才完成,导致报错
  • 3.页面的错误信息,如网络错误、服务器错误等

这里定义BaseCompVM,通过ObservedV2与@Trace @ObservedV2装饰器与@Trace装饰器用于装饰类以及类中的属性,使得被装饰的类和属性具有深度观测的能力。

这里定义BaseCompVM,页面对应的ViewModel继承BaseCompVM 完整的BaseCompVM代码如下

kotlin 复制代码
import CompStateError, { CompErrorType, CompState } from "./CommonViewState";

/// 声明基类的组件ViewModel
/// 可以Observed
@ObservedV2
export default class BaseCompVM {
  /// 定义是否销毁,防止页面销毁后,异步才完成,导致报错
  /// 定义为私有
  private _isDestroy: boolean = false
  // 定义tag
  tag: string = "BaseVM"
  // 定义错误属性,可为null
  @Trace error?: CompStateError | null
  // 定义当前页面的状态
  @Trace viewState: CompState = CompState.idle;

  // 构造函数
  constructor() {
    this._isDestroy = false;
  }

  // 方便调用,减少判断
  setIdle() {
    if (CompState.idle !== this.viewState) {
      this.viewState = CompState.idle;
    }
  }

  setBusy() {
    if (CompState.busy !== this.viewState) {
      this.viewState = CompState.busy;
    }
  }

  setEmpty() {
    if (CompState.empty !== this.viewState) {
      this.viewState = CompState.empty;
    }
  }

  // 状态判断
  isIdle(): boolean {
    return CompState.idle === this.viewState;
  }

  isBusy(): boolean {
    return CompState.busy === this.viewState;
  }

  isEmpty(): boolean {
    return CompState.empty === this.viewState;
  }

  // 是否错误,结合error使用,可显示error的错误信息
  isError(): boolean {
    return CompState.error === this.viewState;
  }

  // 设置错误error
  setError(errorType: CompErrorType = CompErrorType.default, message: string = "", errorMessage: string = "") {
    this.error = new CompStateError({
      errorType: errorType,
      message: message,
      errorMessage: errorMessage,
    });
    this.viewState = CompState.error;
  }

  // 设置默认错误
  setDefaultError(errorMessage: string = "") {
    this.error = new CompStateError({
      errorType: CompErrorType.default,
      message: "",
      errorMessage: errorMessage,
    });
    this.viewState = CompState.error;
  }

  // 获取是否Destroy
  get isDestroy(): boolean {
    return this._isDestroy;
  }

  // toString
  toString(): string {
    return "viewState:" + this.viewState + ", error:" + this.error?.toString() + ", isDestroy:" + this._isDestroy;
  }

  destroy(): void {
    this._isDestroy = true;
  }
}

页面对应的ViewModel继承BaseCompVM,比如HomeVM继承BaseCompVM,在HomeVM中定义数据

3.2、Model层

Model层: 负责从数据库,网络等获取数据等

这里通过请求网络获取数据,定义网络请求的ResponseData类,请求的数据NewsData类,请求错误的httpError类

  • 定义网络请求的ResponseData类
kotlin 复制代码
/// 请求的返回的数据
@ObservedV2
export class ResponseData<T> {
  @Trace state: number = -1;     // 状态码
  @Trace errorMessage?: string;   // 错误信息
  @Trace object?: T

  constructor(state: number, errorMessage: string = "", object: T) {
    this.state = state;
    this.errorMessage = errorMessage;
    this.object = object;
  }

  toString(): string {
    return "state:" + this.state + ", errorMessage:" + this.errorMessage;
  }
}
  • 请求的数据NewsData类
typescript 复制代码
export interface NewsData {
  id?: number
  title?: string
  niceDate?: string
  superChapterName?: string
  link?: string
}
  • 请求错误的httpError类
typescript 复制代码
/// 网络请求Error类
export enum HttpErrorType {
  default,    // 一般错误
  timeout,    // 请求超时
  notFound,   // 未找到404
  unAuth,     // 未认证
}

@ObservedV2
export default class HttpError {
  @Trace errorType:HttpErrorType = HttpErrorType.default;
  @Trace message: string = "";

  constructor(errorType: HttpErrorType = HttpErrorType.default, message: string = "") {
    this.errorType = errorType;
    this.message = message;
  }

  // 默认
  static defaultError(message: string = ""): HttpError {
    return new HttpError(HttpErrorType.default, message);
  }

  toString(): string {
    return "errorType:" + this.errorType + ", message:" + this.message;
  }
}

确定了这些类,我们可以在Model层进行数据请求,比如HomeModel中加载数据

typescript 复制代码
export default class HomeModel {
  constructor() {
  }

  public async loadNews(): Promise<ResponseData<Array<NewsData>>> {
    return new Promise<ResponseData<Array<NewsData>>>((resolve, reject) => {
      axios.get('https://www.wanandroid.com/article/list/0/json').then((response: AxiosResponse) => {
        // console.info(`NewsHttpService Response succeeded: ${response.data["data"]["datas"]}`);
        let newsList = new Array<NewsData>();
        for (let index = 0; index < response.data["data"]["datas"].length; index++) {
          let elem: NewsData = response.data["data"]["datas"][index];
          newsList.push(elem);
        }
        let responseData = new ResponseData(1, "", newsList);
        resolve(responseData);
      }).catch((error: AxiosError) => {
        let httpError = HttpError.defaultError("网络请求失败");
        reject(httpError);
      })
    });
  }
}

这里定义了Model层,可以在HomeVM中使用HomeModel进行数据请求

3.3、HomeVM层

HomeVM继承BaseCompVM,在HomeVM中调用Model层进行数据请求,dataList为请求到的数据

HomeVM代码如下

typescript 复制代码
@ObservedV2
export default class HomeVM extends BaseCompVM {
  @Trace dataList: Array<NewsData> = new Array<NewsData>();
  private isLoading: boolean = false;
  private model: HomeModel = new HomeModel()

  loadData() {
    this.setBusy();
    this.loadNewsData();
  }

  async loadNewsData(isRefresh: boolean = true) : Promise<void> {
    if (this.isLoading === true) {
      return;
    }

    this.isLoading = true;
    this.model.loadNews().then((responseData: ResponseData<Array<NewsData>>) => {
      this.isLoading = false;
      if (this.isDestroy === false) {
        let tmpNewsList: Array<NewsData> = Array();
        if (responseData.object !== null && typeof responseData.object !== 'undefined') {
          for (let index = 0; index < responseData.object.length; index++) {
            let elem: NewsData = responseData.object[index];
            tmpNewsList.push(elem);
          }
        }

        this.dataList = tmpNewsList;

        this.setIdle();
      }
    }).catch((error: HttpError) => {
      if (this.isDestroy === false) {
        this.isLoading = false;
        this.setDefaultError(error.message);
      }
    })
  }

  destroy(): void {
    super.destroy();
  }
}

3.4、View层

View层展示数据,在View层进行展示ViewModel层获取的数据 这里还可以根据状态,显示不同的View

View层的代码如下

scss 复制代码
@ComponentV2
export struct HomeTabsView {
  @Local message: string = 'Hello World';
  @Local homeVM: HomeVM = new HomeVM();

  aboutToAppear(): void {
    this.homeVM.loadData();
  }

  aboutToDisappear(): void {
    this.homeVM.destroy();
  }

  build() {
    Column() {
      if (this.homeVM.isBusy()) {
        Text("正在加载中...")
          .fontSize(12)
          .fontColor($r('app.color.dataset_empty_message'))
      } else if (this.homeVM.isError()) {
        Text(this.homeVM.error?.message)
          .fontSize(12)
          .fontColor($r('app.color.dataset_empty_message'))
      } else if (this.homeVM.isEmpty()) {
        Text('暂无数据哦~')
          .fontSize(12)
          .fontColor($r('app.color.dataset_empty_message'))
      } else {
        List({
          space: 3,
        }) {
          ForEach(this.homeVM.dataList, (item: NewsData) => {
            ListItem() {
              Text(item.title)
                .fontSize(12)
                .fontColor(Color.Black)
                .height(50)
                .backgroundColor(Color.White)
                .width('100%')
            }
          })
        }
        .width($r('app.string.news_List_width'))
        .height('100%')
        .backgroundColor($r('app.color.bg_color'))
      }
    }
    .width('100%')
    .height('100%')
  }
}

四、小结

MVVM是一种常用的架构,MVVM可以帮助我们进行数据层、逻辑层、UI交互层等分离,方便代码的管理、维护。 在鸿蒙学习开发过程中,使用鸿蒙的状态管理所用到的修饰符来进行页面的数据同步展示。

相关推荐
nashane6 小时前
HarmonyOS 6学习:CapsLock键失效诊断与长截图完整实现指南
学习·华为·harmonyos
richard_yuu8 小时前
鸿蒙心理测评模块实战|PHQ-9/GAD7双量表答题、实时计分与结果本地化存储
华为·harmonyos
不爱吃糖的程序媛11 小时前
2026年Electron 鸿蒙PC环境搭建指南
人工智能·华为·harmonyos
nashane11 小时前
HarmonyOS 6学习:长截图功能开发中的滚动拼接与权限处理实战
人工智能·华为·harmonyos
大师兄666812 小时前
从零开发一个 HarmonyOS 输入法——KikaInputMethod 完整拆解
harmonyos·服务卡片·harmonyos6·formkit
Python私教17 小时前
鸿蒙 NEXT 也能接 MCP?用 ArkTS 跑通 AI Agent 工具链
人工智能·华为·harmonyos
Swift社区20 小时前
分布式能力在鸿蒙 PC 上到底怎么用?
分布式·华为·harmonyos
nashane1 天前
HarmonyOS 6学习:外接键盘CapsLock与长截图功能的实战调试与完整解决方案
学习·华为·计算机外设·harmonyos
aqi002 天前
一文理清 HarmonyOS 6.0.2 涵盖的十个升级点
android·华为·harmonyos·鸿蒙·harmony