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交互层等分离,方便代码的管理、维护。 在鸿蒙学习开发过程中,使用鸿蒙的状态管理所用到的修饰符来进行页面的数据同步展示。

相关推荐
塞尔维亚大汉4 小时前
移植案例与原理 - startup子系统之bootstrap_lite服务启动引导部件(1)
harmonyos
轻口味10 小时前
【每日学点鸿蒙知识】跳转三方地图、getStringSync性能、键盘避让模式等
华为·harmonyos
儿歌八万首10 小时前
鸿蒙ArkUI实现部门树列表
华为·harmonyos·鸿蒙·arkui
万少12 小时前
鸿蒙元服务实战-笑笑五子棋(5)
前端·harmonyos·canvas
塞尔维亚大汉12 小时前
移植案例与原理 - startup子系统之syspara_lite系统属性部件
操作系统·harmonyos
HarmonyOS_SDK16 小时前
“面面俱到”!人脸活体检测让应用告别假面攻击
harmonyos
SuperHeroWu719 小时前
【HarmonyOS】鸿蒙应用点9图的处理(draw9patch)
华为·harmonyos·鸿蒙·image·图片拉伸·点9图·不变形
轻口味1 天前
【每日学点鸿蒙知识】多个har依赖、指定编译架构、ArkTS与C++互相调用
c++·华为·harmonyos
gkkk_11 天前
鸿蒙应用开发(2)
华为·harmonyos
HarmonyOS_SDK1 天前
多样化消息通知样式,帮助应用提升日活跃度
harmonyos