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