【HarmonyOS——MVVM模式 | 理解MVVM模式,看这一篇就够了】

大家好,我是学徒小z,近期项目开发中遇到一些数据源放置混乱的问题,所以带来一篇MVVM模式的文章

文章目录

  • MVVM模式
    • 为什么要用MVVM模式
    • 对于鸿蒙中MVVM模式的疑惑
    • ArkUI的MVVM
    • 项目结构中的MVVM
      • 1. 概述
      • 2 .分层说明
      • 3. 架构核心原则
        • 不可跨层访问
        • 下层不可访问上层数据
        • 非父子组件间不可直接访问

MVVM模式

为什么要用MVVM模式

举一个例子,小明是一个开发者,学习了许多基础的知识之后,迫不及待去开发一款APP,这是他第一次开发自己的APP,没有什么经验,所以没有注重APP的项目结构,随着完成了简单的登录注册功能,小明觉得开发一个APP应该很快就能完成,但随着功能模块的增加,一些棘手的问题出现了,数据源的地方到处都有,不知道从哪个地方调用,还有一些重复的地方;想要获取某些数据,发现正常的办法根本行不通,于是使用耗费性能的办法去解决,等等。久而久之,APP最后是写完了,但想要添加新的需求,这个地方要改,那个地方也要改,一不小心,改了一大片,运行不了了。
所以MVVM模式就是要去解决这些问题的,指导开发者更容易的开发和维护产品。

对于鸿蒙中MVVM模式的疑惑

其实很多小伙伴在初次接触到鸿蒙中的MVVM都会感到一些困惑,就比如,诶,为什么找不到viewmodel层在哪里,应该在viewmodel里面放什么东西,在model里面方什么东西,为什么又说@State、@Link就是viewmodel呢等等问题,这些问题,我接下来以我的理解讲一下。

首先就是鸿蒙中的MVVM模式,分为ArkUI的MVVM模式和项目结构中的MVVM模式。这里分开讲是为了讲得清楚一些,其实它们中的viewmodel是一体的,在项目结构中写一个viewmodel层,结构能更加清晰。

ArkUI的MVVM

  • ArkUI采用了MVVM模式,其中ViewModel将数据与视图绑定在一起,更新数据的时候直接更新视图。
    也就是说,一系列装饰器本事就实现了viewmodel的能力,ArkUI中的viewmodel就是装饰器装饰的状态变量。
    关于状态管理最佳实践,本文不再细讲,要不然跑题了,感兴趣的可点击前面的链接

  • 至于model和view,和项目结构中的MVVM一起来讲

    image-20241110204554197

    image-20241110210818468

    image-20241110211809048

项目结构中的MVVM

1. 概述

  • 应用开发中,UI的更新状态需要随着数据的更新而同步更新,这种同步往往决定了应用的性能和用户体验。为了解决UI和数据同步的复杂性,ArkUI采用了Model-View-ViewModel的架构模式。通过这种模式,UI可以随着状态的变化自动更新,无需手动处理。

    • Model:处理与应用数据相关的业务逻辑和数据访问层。它通常包括数据结构、服务层调用和与数据库操作。
    • View:没有业务逻辑,尽量保持“傻瓜化”,通过数据绑定和事件监听与ViewModel交互。
    • ViewModel:负责管理UI状态和交互逻辑。作为连接Model和View的桥梁,ViewModel监控Model数据的变化,通知View更新UI,同时处理用户交互事件并转换为数据操作
  • 状态管理在MVVM模式中扮演者ViewModel的角色,向上刷新UI,向下更新数据,整体框架如下图
    image-20241110212530874

2 .分层说明

  • view

    • 页面组件,比如登录页、列表页等。页面的数据源有可能是相同的,也可能是不同的。

    • 业务组件:本身具备本APP部分业务能力的功能组件,典型的就是这个业务组件可能关联了本项目的ViewModel中的数据,不可以被共享给其他项目使用。

    • 通用组件:和内置组件一样,不会关联到ViewModel中的数据

  • ViewModel层

    • 在用户进入页面的时候,页面的有些数据不一定被访问到,所以最好讲页面数据设计成懒加载的模式。
    • 和model的区别:Model层数据是整个项目的数据,而ViewModel负责对应页面的数据,是整个APP业务数据的一部分,同时进行逻辑处理,也就是对Model的数据继续一定的处理,进行对View的渲染。
    • ViewModel层不只是存放数据,他同时需要提供数据的服务及处理,因此很多框架会以“service”来进行表达此层。
  • Model层

    • 主要负责整个应用的原始数据和数据库操作

    • 一些网络请求获取的数据放在这里,网络请求可以另写为工具类,在该层是调用工具获取原始数据

3. 架构核心原则

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

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

举例说明一下

//错误示例,不符合MVVM原则
@Entry
@Component
export default class MyView extends View {private viewModel = new MyViewModel();@State userInput: string = '';build() {Column() {TextInput().onChange((value) => this.userInput = value);Button('Process').onClick(() => {// ViewModel直接访问View的状态this.viewModel.processInput(this.userInput);});}}
}export class MyViewModel {processInput(input: string) {// 处理逻辑(假设将字符串变为大写)console.log('Processed:', input.toUpperCase());}
}
//正确示例,在MVVM中,ViewModel不应直接依赖View,而是通过数据绑定来进行交互:
@Entry
@Component
export default class MyView extends View {private viewModel = new MyViewModel();@State userInput: string = '';@Link processedOutput: string = this.viewModel.processedOutput;build() {Column() {TextInput().onChange((value) => this.userInput = value);Button('Process').onClick(() => {// 调用ViewModel方法,不直接传递View的值this.viewModel.processInput(this.userInput);});// 显示处理后的输出Text(this.processedOutput);}}
}export class MyViewModel {@State processedOutput: string = '';processInput(input: string) {// 处理逻辑this.processedOutput = input.toUpperCase();}
}
非父子组件间不可直接访问

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

  • 禁止直接访问父组件

    使用eventhub,或者通过在父组件中定义个一个回调函数,将他传给子组件A,子组件通过该函数进行数据处理,返回给父组件,父组件传给子组件B

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

    // ParentComponent.ts
    @Entry
    @Component
    export default class ParentComponent {@State sharedData: string = '';handleDataChange(newData: string) {this.sharedData = newData; // 更新状态,触发子组件的重新渲染}build() {Column() {ComponentA({ onDataChange: (data) => this.handleDataChange(data) })ComponentB({ sharedData: this.sharedData })}}
    }// ComponentA.ts
    export default class ComponentA {@Prop onDataChange: (data: string) => void;someMethod() {this.onDataChange('new value'); // 通过回调通知父组件}
    }// ComponentB.ts
    export default class ComponentB {@Prop sharedData: string; // 接收父组件的状态build() {Text(this.sharedData); // 渲染来自父组件的数据}
    }
  • 我们举一个场景例子说明

    想象一家大公司里有不同的员工和部门,每个部门可以看作一个组件,公司里的每个员工则代表组件中的逻辑或功能。为了确保公司运作高效,各部门之间需要遵循一定的沟通流程:

    总经理代表父组件,负责协调和管理整个公司。
    销售部和财务部代表两个子组件(ComponentA 和 ComponentB)。
    员工之间的交流需要遵循一定的规则,不能随意跨部门沟通。
    错误的沟通方式(违反原则的做法)

    假设销售部的员工小张需要财务部的员工小李帮忙完成一份财务报表。如果小张直接跑到小李的办公室说:“小李,帮我做这份报表”,这就是组件直接相互访问。虽然可以完成工作,但会导致以下问题:

    • 干扰:小张打断了小李的工作流程,造成工作混乱。
    • 混乱:不同部门的人随意要求会造成信息不对称,影响其他工作的协调。

    正确的沟通方式(符合解耦原则)

    • 通过总经理中转
      在规范的公司里,小张不会直接找小李,而是向总经理(父组件)报告需求:“我需要一份财务报表。”总经理会根据工作安排通知财务部,由财务部的员工小李完成这份工作。

      解释

      **销售部(ComponentA)和财务部(ComponentB)**之间没有直接联系。
      总经理(父组件)作为中间人,协调工作和信息流。
      这样做避免了部门之间的直接依赖,确保了各部门的独立性和工作有序性。

    • 通过公司公告(事件总线)
      另一种沟通方式是在公司公告栏发布信息。例如,小张将请求写在公告栏上:“需要财务报表”。任何有能力处理这件事的员工,比如小李,就会看到公告并主动响应。这类似于使用事件总线进行通信。

      解释

      小张不需要知道具体是谁来处理,只需发布请求。

      小李看到公告并进行处理,小张和小李之间没有直接沟通。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/11033.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

网络基础:http协议和内外网划分

声明 学习视频来自B站UP主泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频https://space.bilibili.com/350329294 一,H…

【2024软考架构案例题】你知道 Es 的几种分词器吗?Standard、Simple、WhiteSpace、Keyword 四种分词器你知道吗?

👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主 ⛪️ 个人社区:个人社区 💞 个人主页:个人主页 🙉 专栏地址: ✅ Java 中级 🙉八股文专题:剑指大厂,手撕 J…

CS61b part6

8.6 Implementation Inheritance and Default Method 让我们谈谈另一种类型的继承,这种继承与之前的关系紧密但精神上却非常不同,这种新的继承类型称为实现继承。我们之前看到的是接口继承,在这种方法中,子类获得了方法的签名&am…

C++——异常

异常是在程序执行的过程中发生了某种错误,异常的处理机制允许我们讲发生的异常抛出给程序的另外一部分,对这个错误进行处理。这个机制让问题检测的环节和问题处理的环节分离。检测环节只需要负责检测即可,无需关系解决的细节问题。在C语言中处…

『VUE』19. scope避免组件之间样式互相覆盖(详细图文注释)

目录 使用多个组件带有样式分析如何避免css覆盖总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 使用多个组件带有样式 ComPonent1.vue <template><h3>ComPonent1.vue</h3> </template><script&g…

CUDA解说

CUDA&#xff08;Compute Unified Device Architecture&#xff09;是NVIDIA公司开发的一种并行计算平台和编程模型。 它允许开发者使用NVIDIA的GPU&#xff08;图形处理单元&#xff09;进行通用计算&#xff0c;即GPGPU&#xff08;General-Purpose computing on Graphics P…

海量日志收集ELK实战(docker部署ELK)从日志中挖取宝贵数据

文章目录 一、准备工作1.1 服务器配置要求1.2 关闭防火墙1.3 创建docker网络 二、docker安装elasticsearch2.1 下载 Elastic Search 镜像2.2 创建宿主机的挂载目录2.3 设置宿主机max_map_count2.5 docker启动命令2.6 关闭es容器密码安全验证2.7 重启es容器2.8 测试安装成功2.9 …

nacos占用内存过高问题

1. 问题 在微服务项目的学习和开发过程中&#xff0c;服务注册中心 Nacos 是一个必不可少的组件。Nacos 提供了服务注册、配置管理等核心功能&#xff0c;使得分布式服务可以轻松实现互相发现、负载均衡和动态配置。然而&#xff0c;许多微服务项目中包含多个模块&#xff0c;…

JavaScript核心编程 - 原型链 作用域 与 执行上下文

原型 在JavaScript中&#xff0c;每个对象都有一个内部属性&#xff0c;称为__proto__&#xff08;在ES6中&#xff0c;这个属性被Object.getPrototypeOf()和Object.setPrototypeOf()方法标准化&#xff09;&#xff0c;这个属性指向该对象的原型。原型本身也是一个对象&#…

C++ 引用 详解

引用 引用 不是新定义一个变量&#xff0c;而 是给已存在变量取了一个别名 &#xff0c;编译器不会为引用变量开辟内存空 间&#xff0c;它和它引用的变量 共用同一块内存空间。 比如&#xff1a; 李逵 &#xff0c;在家称为 " 铁牛 " &#xff0c;江湖上人称 &qu…

计算机视觉中的中值滤波:经典案例与Python代码解析

Hey小伙伴们&#xff01;今天我们要聊的是计算机视觉中的一个重要技术——中值滤波。中值滤波是一种非线性滤波方法&#xff0c;主要用于去除图像中的椒盐噪声&#xff0c;同时保留图像的边缘和细节。通过中值滤波&#xff0c;我们可以显著改善图像的质量。让我们一起来看看如何…

【C++练习】计算前N项自然数之和

题目&#xff1a; 计算前N项自然数之和 描述&#xff1a; 编写一个C程序&#xff0c;要求用户输入一个整数N&#xff0c;然后计算并输出从1到N&#xff08;包括N&#xff09;的所有自然数之和。 程序功能要求&#xff1a; 程序首先提示用户输入一个整数N。使用一个循环结构…

SpringBoot日志配置

Spring默认日志风格 个人感觉默认的格式还是不错的&#xff0c;每一列都可以对其&#xff0c;而且能用颜色区分&#xff0c;查看日志明了&#xff1b; 下面是他默认的 pattern的配置 %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(${PID:- …

Vue3学习笔记(上)

Vue3学习笔记&#xff08;上&#xff09; Vue3的优势&#xff1a; 更容易维护&#xff1a; 组合式API更好的TypeScript支持 更快的速度&#xff1a; 重写diff算法模板编译优化更高效的组件初始化 更小的体积&#xff1a; 良好的TreeShaking按需引入 更优的数据响应式&#xf…

看懂本文,入门神经网络Neural Network

神经网络&#xff08;Neural Network&#xff09; 1.1图片 每一个图片都是三维数组&#xff0c;每个像素的值为0-255&#xff0c;如 训练集Training Dataset&#xff1a;“上课学的知识”&#xff0c;用于训练模型得到参数 验证集Validation Dataset&#xff1a;“课后习题”…

Zoho Books助外贸,应收账款简化管

ZohoBooks财务管理软件助外贸企业精准管理客户信息&#xff0c;简化跨境开票&#xff0c;集成支付网关自动对账&#xff0c;智能提醒跟进账款&#xff0c;提供强大报表分析功能&#xff0c;支持多币种和当地税法&#xff0c;促进财务健康与资金回笼。 一、精准的客户信息管理 …

保姆级教程!!教你通过【Pycharm远程】连接服务器运行项目代码

小罗碎碎念 这篇文章主要解决一个问题——我有服务器&#xff0c;但是不知道怎么拿来写代码&#xff0c;跑深度学习项目。确实&#xff0c;玩深度学习的成本比较高&#xff0c;无论是前期的学习成本&#xff0c;还是你需要具备的硬件成本&#xff0c;都是拦路虎。小罗没有办法…

作业调度和程序装入内存

作业调度 我们知道&#xff0c;磁盘上的可执行程序只有装入内存&#xff0c;成为进程才可以运行。在磁盘上有许多的可执行程序等待被操作系统唤入内存执行&#xff0c;我们把可执行程序在磁盘上的调度称之为作业调度。 注意&#xff1a;这种说法听起来好像是作业在磁盘上的调…

广义布里渊区方程推导过程中一个公式的理解

是对DOI: 10.1103/PhysRevLett.123.066404补充材料公式(S25)的理解 clear;clc;close all q2; N1;Mq*N; syms LMatsym(zeros(2*M,2*M));for ii1:MTp[];for jj1:2*M%eval([syms , f,num2str(ii),num2str(jj)]);eval([syms ,f,num2str(ii),_beta,num2str(jj),_ES])%eval([temp,f,…

嵌入式linux中HDMI驱动操作方法

大家好,今天主要给大家分享一下,linux系统里面的HDMI驱动实现方法。 第一:HDMI基本简介 HDMI 全称为 High Definition Multimedia Interface,也就是高清多媒体接口,是一个纯数字的音视频传输接口,通过一根线同时发送音视频数据。目前在电视、显示器、电脑、机顶盒等领域得…