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

相关推荐
别说我什么都不会15 小时前
ohos.net.http请求HttpResponse header中set-ccokie值被转成array类型
网络协议·harmonyos
码是生活15 小时前
鸿蒙开发排坑:解决 resourceManager.getRawFileContent() 获取文件内容为空问题
前端·harmonyos
鸿蒙场景化示例代码技术工程师15 小时前
基于Canvas实现选座功能鸿蒙示例代码
华为·harmonyos
小脑斧爱吃鱼鱼16 小时前
鸿蒙项目笔记(1)
笔记·学习·harmonyos
鸿蒙布道师17 小时前
鸿蒙NEXT开发对象工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
zhang10620917 小时前
HarmonyOS 基础组件和基础布局的介绍
harmonyos·基础组件·基础布局
马剑威(威哥爱编程)17 小时前
在HarmonyOS NEXT 开发中,如何指定一个号码,拉起系统拨号页面
华为·harmonyos·arkts
GeniuswongAir19 小时前
Flutter极速接入IM聊天功能并支持鸿蒙
flutter·华为·harmonyos
90后的晨仔1 天前
鸿蒙ArkUI框架中的状态管理
harmonyos
别说我什么都不会2 天前
OpenHarmony 5.0(API 12)关系型数据库relationalStore 新增本地数据变化监听接口介绍
api·harmonyos