HarmonyOS 5.0应用开发——MVVM模式的应用

【高心星出品】

MVVM模式的应用

MVVM(Model-View-ViewModel)模式是一种广泛用于应用开发的架构模式,它有助于分离应用程序的业务逻辑、数据和用户界面。在鸿蒙(HarmonyOS)开发中,MVVM模式被广泛应用,特别是在使用ArkUI框架时。

ArkUI采用了 Model-View-ViewModel(MVVM)架构模式。MVVM 将应用分为Model、View和ViewModel三个核心部分,实现数据、视图与逻辑的分离。通过这种模式,UI可以随着状态的变化自动更新,无需手动处理,从而更加高效地管理数据和视图的绑定与更新。

  • Model:负责存储和管理应用的数据以及业务逻辑,不直接与用户界面交互。通常从后端接口获取数据,是应用程序的数据基础,确保数据的一致性和完整性。
  • View:负责用户界面展示数据并与用户交互,不包含任何业务逻辑。它通过绑定ViewModel层提供的数据来动态更新UI。
  • ViewModel:负责管理UI状态和交互逻辑。作为连接Model和View的桥梁,通常一个View对应一个ViewModel,ViewModel监控Model数据的变化,通知View更新UI,同时处理用户交互事件并转换为数据操作。
ArkUI开发模式图

ArkUI的UI开发开发模式即是MVVM模式,而状态变量在MVVM模式中扮演着ViewModel的角色,向上刷新UI,向下更新数据,整体框架如下图:

在这里插入图片描述

架构设计原则

不可跨层访问

  • View层不可以直接调用Model层的数据,只能通过ViewModel提供的方法进行调用。
  • Model层数据,不可以直接操作UI,Model层只能通知ViewModel层数据有更新,由ViewModel层更新对应的数据。

下层不可访问上层数据

下层的数据通过通知模式更新上层数据。在业务逻辑中,下层不可直接写代码去获取上层数据。如ViewModel层的逻辑处理,不能去依赖View层界面上的某个值。

非父子组件间不可直接访问

这是针对View层设计的核心原则,一个组件应该具备这样的逻辑:

  • 禁止直接访问父组件(必须使用事件或是订阅能力)。
  • 禁止直接访问兄弟组件能力。这是因为组件应该仅能访问自己看的见的子节点(通过传参)和父节点(通过事件或通知),以此完成组件之间的解耦。

对于一个组件,这样设计的原因是:

  • 组件自己使用了哪些子组件是明确的,因此可以访问。
  • 组件被放置于哪个父节点下是未知的,因此组件想访问父节点,就只能通过通知或者事件能力完成。
  • 组件不可能知道自己的兄弟节点是谁,因此组件不可以操纵兄弟节点。
案例

使用MVVM模式开发一个备忘录应用。

运行效果

在这里插入图片描述

项目结构
mvvmdemo/
├── src/
│   └── main/
│       ├── ets/
│       │   ├── common/         # 公共组件和常量
│       │   ├── entryability/   # 入口能力
│       │   ├── model/          # 数据模型
│       │   ├── utils/          # 工具类
│       │   ├── view/           # 视图组件
│       │   └── viewmodel/      # 视图模型
│       └── resources/          # 资源文件
功能特性
  • 显示待办事项列表
  • 添加新的待办事项
  • 标记待办事项为已完成/未完成
  • 删除待办事项
  • 错误处理和加载状态显示
开发环境
  • DevEco Studio 5.0或更高版本
  • HarmonyOS SDK API 14
  • ArkTS
model层

负责描述每一条item数据的结构以及数据的业务逻辑。

TodoItem:

export class TodoItem {
  id: number;
  title: string;
  completed: boolean;
  createTime: string;

  constructor(id:number,title: string) {
    this.id=id
    this.title = title;
    this.completed = false;
    this.createTime = new Date().toLocaleString();
  }

    toggleComplete(): void {
    this.completed = !this.completed;
  }
} 

TodoService:

import { TodoItem } from './TodoItem';

/**
 * 待办事项服务类
 * 负责数据的增删改查操作
 */
export class TodoService {
  private static instance: TodoService;
  private todoList: TodoItem[] = [];

  private constructor() {
    // 初始化一些示例数据
    this.todoList = [
      new TodoItem(1,'学习鸿蒙开发'),
      new TodoItem(2,'完成MVVM示例项目'),
      new TodoItem(3,'阅读鸿蒙文档')
    ];

  }

  /**
   * 获取单例实例
   */
  public static getInstance(): TodoService {
    if (!TodoService.instance) {
      TodoService.instance = new TodoService();
    }
    return TodoService.instance;
  }

  /**
   * 获取所有待办事项
   */
  public getAllTodos(): TodoItem[] {
    return [...this.todoList]
  }

  /**
   * 添加待办事项
   */
  public addTodo(title: string) {

       const todo = new TodoItem(this.todoList[this.todoList.length-1].id+1,title);
       this.todoList.push(todo);
  }

  /**
   * 删除待办事项
   */
  public deleteTodo(id: number): boolean {
    const index = this.todoList.findIndex(item => item.id === id);
    if (index !== -1) {
     this.todoList.splice(index, 1);
      return true;
    }
    return false;
  }

  /**
   * 切换待办事项状态
   */
  public toggleTodo(id: number): boolean {
    const todo = this.todoList.find(item => item.id === id);
    if (todo) {
      todo.toggleComplete();
      return true;
    }
    return false;
  }
} 
viewmodel层

TodoViewModel:

import { TodoItem } from '../model/TodoItem';
import { TodoService } from '../model/TodoService';

/**
 * 待办事项ViewModel类
 * 负责连接Model和View,处理业务逻辑
 */
@Observed

export class TodoViewModel {
  private todoService: TodoService;
  
  @Track
   todoList: TodoItem[] = [];
  @Track
  newTodoTitle: string = '';
  @Track
  isLoading: boolean = false;
  @Track
   errorMessage: string = '';
  
  constructor() {
    this.todoService = TodoService.getInstance();
    this.loadTodos();
  }
  
  /**
   * 加载所有待办事项
   */
  public loadTodos(): void {
    this.isLoading = true;
    this.errorMessage = '';
    
    try {
      this.todoList = this.todoService.getAllTodos();
    } catch (error) {
      this.errorMessage = '加载待办事项失败';
      console.error('加载待办事项失败:', error);
    } finally {
      this.isLoading = false;
    }
  }
  
  /**
   * 添加待办事项
   */
  public async addTodo(){
    if (!this.newTodoTitle.trim()) {
      this.errorMessage = '待办事项不能为空';
      return;
    }
    
    try {
     this.todoService.addTodo(this.newTodoTitle);
      this.todoList=this.todoService.getAllTodos()
      console.log('gxxt add ',JSON.stringify(this.todoList))
      this.newTodoTitle = '';
      this.errorMessage = '';
    } catch (error) {
      this.errorMessage = '添加待办事项失败';
      console.error('添加待办事项失败:', error);
    }
  }
  
  /**
   * 删除待办事项
   */
  public deleteTodo(id: number): void {
    try {
      if (this.todoService.deleteTodo(id)) {
        this.todoList=this.todoService.getAllTodos()
        console.log('gxxt del ',JSON.stringify(this.todoList))
      }
    } catch (error) {
      this.errorMessage = '删除待办事项失败';
      console.error('删除待办事项失败:', error);
    }
  }
  
  /**
   * 切换待办事项状态
   */
  public toggleTodo(id: number): void {
    try {
      if (this.todoService.toggleTodo(id)) {
        // 更新列表中的项目状态
        console.log('gxxt todolist ',JSON.stringify(this.todoList))
       this.todoList=this.todoService.getAllTodos()
      }
    } catch (error) {
      this.errorMessage = '更新待办事项状态失败';
      console.error('更新待办事项状态失败:', error);
    }
  }
  
  /**
   * 设置新待办事项标题
   */
  public setNewTodoTitle(title: string): void {
    this.newTodoTitle = title;
  }
  
  /**
   * 清除错误信息
   */
  public clearError(): void {
    this.errorMessage = '';
  }
} 
view层

TodoPage:

import { TodoItem } from '../model/TodoItem';
import { TodoItemView } from '../view/TodoItemView';
import { TodoViewModel } from '../viewmodel/TodoViewModel';

/**
 * 待办事项主页面
 */
@Entry
@Component
struct TodoPage {
  @State private viewModel: TodoViewModel = new TodoViewModel();
  
  build() {
    Column() {
      // 标题栏
      Row() {
        Text('待办事项')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .height(60)
      .padding({ left: 20, right: 20 })
      .backgroundColor('#F5F5F5')
      
      // 添加待办事项区域
      Row() {
        TextInput({ placeholder: '输入新的待办事项...' })
          .width('80%')
          .height(40)
          .backgroundColor('#FFFFFF')
          .onChange((value: string) => {
            this.viewModel.setNewTodoTitle(value);
          })
        
        Button('添加')
          .width('20%')
          .height(40)
          .backgroundColor('#007DFF')
          .onClick(() => {
            this.viewModel.addTodo();
          })
      }
      .width('90%')
      .margin({ top: 20, bottom: 10 })
      
      // 错误信息
      if (this.viewModel.errorMessage) {
        Text(this.viewModel.errorMessage)
          .fontSize(14)
          .fontColor('#FF0000')
          .margin({ bottom: 10 })
      }
      
      // 待办事项列表
      if (this.viewModel.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
          .margin({ top: 50 })
      } else {
        List() {
          ForEach(this.viewModel.todoList, (item:TodoItem) => {
            ListItem() {
              TodoItemView({
                todo: item,
                onToggle: (id) => this.viewModel.toggleTodo(id),
                onDelete: (id) => this.viewModel.deleteTodo(id)
              })
            }
          })
        }
        .width('90%')
        .layoutWeight(1)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
} 

TodoItemView:

import { TodoItem } from '../model/TodoItem';

/**
 * 待办事项视图组件
 */
@Component
export struct TodoItemView {
 @State private  todo: TodoItem=new TodoItem(0,'')
  private onToggle: ((id: number) => void)=(id:number)=>{};
  private onDelete: ((id: number) => void)=(id:number)=>{};
  
  build() {
    Row() {
      // 复选框
      Toggle({ type: ToggleType.Checkbox, isOn: this.todo.completed })
        .onChange(() => {
          this.onToggle(this.todo.id);
        })
        .margin({ right: 10 })
      
      // 待办事项标题
      Text(this.todo.title)
        .fontSize(16)
        .fontColor(this.todo.completed ? '#999999' : '#000000')
        .decoration({ type:this.todo.completed ? TextDecorationType.LineThrough : TextDecorationType.None})
        .layoutWeight(1)
      
      // 删除按钮
      Button() {
        Image($r('sys.media.ohos_ic_public_device_watch'))
          .width(20)
          .height(20)
      }
      .type(ButtonType.Circle)
      .backgroundColor(Color.Transparent)
      .onClick(() => {
        this.onDelete(this.todo.id);
      })
    }
    .width('100%')
    .padding(10)
    .backgroundColor('#F5F5F5')
    .borderRadius(8)
    .margin({ bottom: 10 })
  }
} 
  
鸿蒙系统中,MVVM(Model-View-ViewModel)架构模式是一种用于分离 UI 层与业务逻辑层的设计模式。通过 MVVM 模式,开发者可以更好地组织代码结构,提高可维护性和可测试性。以下是关于鸿蒙系统中 MVVM 架构的实现方式和使用方法的详细说明: ### 实现方式 1. **ArkUI 的 MVVM 模式**: 鸿蒙的 ArkUI 支持 MVVM 模式,其核心是 ViewModel 层。ViewModel 作为 Model 和 View 之间的桥梁,负责管理数据并处理用户交互逻辑。通过 ViewModel,可以从 Model 获取数据,并将其转换为适合 View 展示的形式 [^2]。 2. **项目结构中的 MVVM 模式**: 在项目的整体结构设计中,MVVM 模式通常被分为三层: - **Model**:负责处理数据来源,例如从本地数据库或网络接口获取数据。 - **ViewModel**:负责处理数据逻辑以及与 View 的交互,将 Model 中的数据转化为 View 可以展示的状态。 - **View**:负责 UI 的展示和用户的输入事件捕获,但不包含任何业务逻辑。 在实际开发中,可以在项目结构中单独创建一个 `viewmodel` 层来存放所有 ViewModel 类,这样可以使整个项目的结构更加清晰 [^2]。 3. **状态管理**: ViewModel 层不仅可以从 Model 获取数据,还可以处理状态更新。例如,当用户触发某个操作时,ViewModel 会根据操作更新 Model 数据,并通知 View 进行刷新 [^4]。 --- ### 使用教程 #### 1. 创建 Model Model 是数据的来源,可能包括网络请求、本地存储等。以下是一个简单的 Model 示例: ```typescript // Model 层 class User { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } // 数据服务类 class UserService { private users: User[] = []; constructor() { // 初始化一些数据 this.users.push(new User("Alice", 25)); this.users.push(new User("Bob", 30)); } getUsers(): User[] { return this.users; } } ``` #### 2. 创建 ViewModel ViewModel 负责连接 Model 和 View。它从 Model 获取数据,并将其转换为 View 所需的状态。以下是一个 ViewModel 示例: ```typescript // ViewModel 层 class UserViewModel { private userService: UserService; public userList: User[] = []; constructor() { this.userService = new UserService(); this.loadUsers(); } private loadUsers(): void { this.userList = this.userService.getUsers(); } public addUser(user: User): void { this.userService.addUser(user); this.userList = this.userService.getUsers(); } } ``` #### 3. 创建 View View 负责 UI 的展示和用户交互。在鸿蒙中,可以通过 ArkTS 编写 UI 组件,并绑定到 ViewModel 上的数据。以下是一个简单的 View 示例: ```typescript // View 层 @Component struct UserListView { @State viewModel: UserViewModel = new UserViewModel(); build() { Column() { Text("用户列表") .fontSize(24) .fontWeight(FontWeight.Bold) ForEach(this.viewModel.userList, (user: User) => { Text(`${user.name} - ${user.age}`) .fontSize(18) .onClick(() => { // 处理点击事件 console.log(`Clicked on ${user.name}`); }) }) } .width('100%') .padding(10) } } ``` --- #### 4. 状态管理和数据绑定 在鸿蒙中,ViewModel 可以通过状态管理机制与 View 进行绑定。当 ViewModel 中的数据发生变化时,View 会自动更新。这可以通过 `@State` 或 `@Prop` 等装饰器实现 [^4]。 --- ### 总结 鸿蒙系统的 MVVM 架构模式通过分层设计,使得代码结构更清晰、易于维护。开发者可以根据需求选择合适的 ViewModel 实现方式,并利用状态管理机制简化数据绑定过程。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值