news 2026/7/30 13:42:15

ArkTS中的MVC、MVP、MVVM

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ArkTS中的MVC、MVP、MVVM

在 ArkTS 和 ArkUI 开发中,一个页面通常包含三部分内容:

  • 页面展示;
  • 用户交互;
  • 数据请求和业务处理。

如果所有代码都写在一个@Component中,随着功能增加,组件会变得越来越臃肿。MVC、MVP 和 MVVM 的作用,就是将页面、业务和数据进行合理拆分。

本文使用同一个“登录页面”来理解这三种架构。


一、三种架构共同解决的问题

假设登录页面需要完成以下功能:

  1. 获取用户输入的账号和密码;
  2. 校验账号和密码;
  3. 调用登录接口;
  4. 显示加载状态;
  5. 显示登录成功或失败;
  6. 登录成功后跳转页面。

如果这些逻辑全部写在页面组件中:

@Entry@Componentstruct LoginPage{@Stateaccount:string=''@Statepassword:string=''@Stateloading:boolean=false@Statemessage:string=''privateasynclogin():Promise<void>{// 参数校验// 网络请求// 错误处理// 页面状态修改// 页面跳转}build(){// 页面布局}}

随着功能增加,LoginPage会同时负责:

  • UI 布局;
  • 状态管理;
  • 输入校验;
  • 网络请求;
  • 数据转换;
  • 错误处理;
  • 页面跳转。

这类组件通常被称为“胖组件”。

MVC、MVP、MVVM 的核心目标,就是让不同对象承担不同职责。


二、公共的 Model 层

无论采用哪种架构,Model 的职责基本一致。

Model 负责:

  • 数据实体;
  • 网络请求;
  • 数据库存储;
  • 业务数据处理;
  • 底层服务能力。

例如:

classUser{id:numbername:stringconstructor(id:number,name:string){this.id=idthis.name=name}}classLoginService{asynclogin(account:string,password:string):Promise<User>{if(account==='admin'&&password==='123456'){returnnewUser(1,'管理员')}thrownewError('账号或密码错误')}}

LoginService只负责完成登录业务,不关心页面中使用的是:

Text Button TextInput LoadingProgress

Model 不应该依赖具体 UI。


三、MVC:Controller 直接协调 Model 和 View

MVC 分为:

Model View Controller

其中:

  • Model:负责数据和业务能力;
  • View:负责界面展示;
  • Controller:接收用户事件,调用 Model,并更新 View。

在 ArkUI 中,@Component经常同时承担 View 和 Controller 的职责:

build() → View 事件处理方法 → Controller

MVC 示例

@Entry@Componentstruct LoginMvcPage{@Stateaccount:string=''@Statepassword:string=''@Stateloading:boolean=false@Statemessage:string=''privateloginService:LoginService=newLoginService()privateasynconLoginClick():Promise<void>{if(this.account.length===0){this.message='请输入账号'return}if(this.password.length<6){this.message='密码不能少于 6 位'return}this.loading=truethis.message=''try{constuser=awaitthis.loginService.login(this.account,this.password)this.message=`欢迎你,${user.name}`}catch(error){this.message='账号或密码错误'}finally{this.loading=false}}build(){Column({space:16}){TextInput({placeholder:'请输入账号',text:this.account}).onChange((value:string)=>{this.account=value})TextInput({placeholder:'请输入密码',text:this.password}).onChange((value:string)=>{this.password=value})Button(this.loading?'登录中...':'登录').enabled(!this.loading).onClick(()=>{this.onLoginClick()})if(this.loading){LoadingProgress()}Text(this.message)}.padding(20)}}

MVC 的数据流

用户点击 ↓ View 触发事件 ↓ Controller 处理逻辑 ↓ Controller 调用 Model ↓ Model 返回结果 ↓ Controller 修改页面状态 ↓ View 重新渲染

可以概括为:

View → Controller → Model View ← Controller ← Model

MVC 的特点

MVC 最大的特点是:

Controller 直接读取 View 的数据,也直接修改 View 的状态。

例如:

this.loading=truethis.message='登录失败'

这种方式简单直接,适合业务较少的页面。

但页面复杂后,Controller 逻辑容易全部堆积在组件中,最终形成“胖组件”。


四、MVP:Presenter 通过接口命令 View

MVP 分为:

Model View Presenter

Presenter 可以理解为从 Controller 中抽离出来的业务协调者。

它负责:

  • 获取用户输入;
  • 校验数据;
  • 调用 Model;
  • 处理业务结果;
  • 命令 View 更新页面。

MVP 的核心是:

Presenter 不直接操作具体 UI,而是通过 View 接口控制页面。


定义 View 接口

interfaceLoginViewContract{getAccount():stringgetPassword():stringshowLoading():voidhideLoading():voidshowMessage(message:string):voidshowLoginSuccess(user:User):void}

这个接口规定登录页面必须具备哪些能力。

Presenter 只认识这个接口,并不知道页面具体使用的是Text、Toast 还是 Dialog。


Presenter 实现

classLoginPresenter{privateview:LoginViewContractprivateloginService:LoginServiceconstructor(view:LoginViewContract,loginService:LoginService){this.view=viewthis.loginService=loginService}asynclogin():Promise<void>{constaccount=this.view.getAccount()constpassword=this.view.getPassword()if(account.length===0){this.view.showMessage('请输入账号')return}if(password.length<6){this.view.showMessage('密码不能少于 6 位')return}this.view.showLoading()try{constuser=awaitthis.loginService.login(account,password)this.view.showLoginSuccess(user)}catch(error){this.view.showMessage('账号或密码错误')}finally{this.view.hideLoading()}}}

Presenter 中没有出现任何 ArkUI 控件:

@Component @State TextInput Button Text

因此 Presenter 可以脱离页面独立测试。


MVP 页面

@Entry@Componentstruct LoginMvpPage{@Stateaccount:string=''@Statepassword:string=''@Stateloading:boolean=false@Statemessage:string=''privatepresenter:LoginPresenter|null=nullaboutToAppear():void{constview:LoginViewContract={getAccount:():string=>{returnthis.account},getPassword:():string=>{returnthis.password},showLoading:():void=>{this.loading=true},hideLoading:():void=>{this.loading=false},showMessage:(message:string):void=>{this.message=message},showLoginSuccess:(user:User):void=>{this.message=`欢迎你,${user.name}`}}this.presenter=newLoginPresenter(view,newLoginService())}build(){Column({space:16}){TextInput({placeholder:'请输入账号',text:this.account}).onChange((value:string)=>{this.account=value})TextInput({placeholder:'请输入密码',text:this.password}).onChange((value:string)=>{this.password=value})Button(this.loading?'登录中...':'登录').enabled(!this.loading).onClick(()=>{this.presenter?.login()})if(this.loading){LoadingProgress()}Text(this.message)}.padding(20)}}

MVP 的数据流

用户点击 ↓ View 将事件交给 Presenter ↓ Presenter 获取 View 输入 ↓ Presenter 调用 Model ↓ Model 返回结果 ↓ Presenter 调用 View 接口 ↓ View 修改页面状态 ↓ 页面重新渲染

可以概括为:

View ↔ Presenter ↔ Model

MVP 的核心特征

Presenter 会调用:

this.view.showLoading()this.view.showMessage('登录失败')this.view.showLoginSuccess(user)

这些代码表达的是:

View,请显示加载状态 View,请显示错误信息 View,请显示登录成功

所以 MVP 的本质是:

Presenter 输出的是行为和命令。

Presenter 虽然不认识具体控件,但它知道 View 可以执行哪些操作。


五、MVVM:ViewModel 提供页面状态

MVVM 分为:

Model View ViewModel

ViewModel 负责:

  • 保存页面状态;
  • 接收页面输入;
  • 处理输入校验;
  • 调用 Model;
  • 转换页面数据;
  • 提供页面可以直接使用的状态。

MVVM 的核心是:

ViewModel 不命令 View 做什么,只修改自身状态,View 根据状态重新渲染。


ViewModel 实现

@ObservedclassLoginViewModel{account:string=''password:string=''loading:boolean=falsemessage:string=''privateloginService:LoginServiceconstructor(loginService:LoginService){this.loginService=loginService}getloginEnabled():boolean{returnthis.account.length>0&&this.password.length>=6&&!this.loading}setAccount(account:string):void{this.account=account}setPassword(password:string):void{this.password=password}asynclogin():Promise<void>{if(this.account.length===0){this.message='请输入账号'return}if(this.password.length<6){this.message='密码不能少于 6 位'return}this.loading=truethis.message=''try{constuser=awaitthis.loginService.login(this.account,this.password)this.message=`欢迎你,${user.name}`}catch(error){this.message='账号或密码错误'}finally{this.loading=false}}}

ViewModel 中没有:

Text Button TextInput LoadingProgress

它只维护页面状态。


MVVM 页面

@Entry@Componentstruct LoginMvvmPage{@StateviewModel:LoginViewModel=newLoginViewModel(newLoginService())build(){Column({space:16}){TextInput({placeholder:'请输入账号',text:this.viewModel.account}).onChange((value:string)=>{this.viewModel.setAccount(value)})TextInput({placeholder:'请输入密码',text:this.viewModel.password}).onChange((value:string)=>{this.viewModel.setPassword(value)})Button(this.viewModel.loading?'登录中...':'登录').enabled(this.viewModel.loginEnabled).onClick(()=>{this.viewModel.login()})if(this.viewModel.loading){LoadingProgress()}Text(this.viewModel.message)}.padding(20)}}

MVVM 的数据流

用户输入或点击 ↓ View 调用 ViewModel ↓ ViewModel 调用 Model ↓ Model 返回结果 ↓ ViewModel 修改自身状态 ↓ View 读取新状态 ↓ 页面重新渲染

可以概括为:

View ↔ ViewModel ↔ Model

MVVM 的核心特征

ViewModel 只会修改:

this.loading=truethis.message='登录失败'

它不会调用:

this.view.showLoading()this.view.showMessage()

View 根据状态决定页面如何显示:

if(this.viewModel.loading){LoadingProgress()}Text(this.viewModel.message)

因此 MVVM 的本质是:

ViewModel 输出的是状态和数据。


六、MVP 和 MVVM 的核心区别

MVP 和 MVVM 都把业务逻辑从页面组件中抽离,但它们与 View 的通信方式不同。

MVP:Presenter 命令 View

this.view.showLoading()this.view.showMessage('登录失败')

Presenter 的意思是:

View,你现在显示加载。 View,你现在显示错误。

Presenter 输出的是行为。

MVVM:ViewModel 修改状态

this.loading=truethis.message='登录失败'

ViewModel 的意思是:

我现在处于加载状态。 我现在有一条错误信息。

View 根据这些状态决定如何渲染。

ViewModel 输出的是状态。

因此可以记住:

Presenter 输出行为 ViewModel 输出状态

七、Model 和 ViewModel 的区别

Model 表示真实业务数据。

例如:

classUser{firstName:string=''lastName:string=''age:number=0vip:boolean=false}

这些是服务器或数据库中的原始数据。

但页面可能需要显示:

张三 18岁 VIP会员

ViewModel 可以将 Model 转换成适合页面直接展示的数据:

classUserViewModel{displayName:string=''ageText:string=''vipText:string=''update(user:User):void{this.displayName=`${user.firstName}${user.lastName}`this.ageText=`${user.age}`this.vipText=user.vip?'VIP会员':'普通用户'}}

所以:

Model:真实业务数据 ViewModel:页面需要的数据和状态

八、三种架构的核心对比

架构中间层业务逻辑位置UI 更新方式
MVCControllerComponent 或 ControllerController 直接修改状态
MVPPresenterPresenterPresenter 通过接口命令 View
MVVMViewModelViewModelView 根据 ViewModel 状态渲染

进一步理解:

问题MVCMVPMVVM
谁处理点击事件ControllerPresenterViewModel
谁执行输入校验ControllerPresenterViewModel
谁调用 ModelControllerPresenterViewModel
谁修改 UIControllerViewView
中间层输出什么直接操作行为、命令状态、数据
是否依赖状态绑定不强制不强制通常依赖
测试难度相对较高较低较低

九、ArkTS 项目应该如何选择

简单页面使用 MVC

例如:

  • 关于页面;
  • 静态详情页;
  • 简单设置页;
  • 只有少量状态和交互的页面。

这类页面直接在 Component 中处理逻辑即可,没必要创建大量额外对象。

流程型页面适合 MVP

例如:

  • 多步骤注册;
  • 实名认证;
  • 权限申请;
  • 复杂表单提交;
  • 分步骤业务流程。

这类页面经常需要明确控制 View:

显示下一步 隐藏当前区域 弹出错误提示 进入成功页面

Presenter 输出行为会比较直观。

状态复杂的页面适合 MVVM

例如:

  • 下载页面;
  • 播放器页面;
  • 聊天页面;
  • 购物车页面;
  • 股票行情页面;
  • 多状态列表页面。

这些页面通常有大量状态:

加载中 加载成功 加载失败 空数据 刷新中 是否可点击 下载进度 播放进度 未读数量

MVVM 可以把这些状态集中放在 ViewModel 中,由 View 声明式渲染。


十、为什么 MVVM 与 ArkUI 很契合

ArkUI 是声明式 UI。

开发者不是一步一步命令页面:

创建 Loading 添加 Loading 隐藏按钮 修改文字 移除 Loading

而是描述页面在某种状态下应该是什么样子:

if(this.viewModel.loading){LoadingProgress()}Button('登录').enabled(this.viewModel.loginEnabled)Text(this.viewModel.message)

当状态变化时,ArkUI 会重新计算和渲染相关 UI。

这与 MVVM 的状态驱动思想天然一致:

ViewModel 管理状态 View 描述状态对应的界面

十一、任何架构都可能变得臃肿

MVC 可能出现:

Massive Component

MVP 可能出现:

Massive Presenter

MVVM 可能出现:

Massive ViewModel

如果 ViewModel 同时负责:

  • 网络请求;
  • 数据库存储;
  • JSON 解析;
  • 页面跳转;
  • 权限申请;
  • 埋点;
  • 文件下载;
  • 缓存处理;
  • 页面状态;

那么它同样会非常臃肿。

更合理的结构可以继续拆分为:

View ↓ ViewModel / Presenter ↓ UseCase / Service ↓ Repository ↓ 网络和数据库

架构的重点不是类名,而是职责是否清晰。


十二、最终总结

MVC、MVP 和 MVVM 都是在解决页面代码职责混乱的问题。

MVC

View 触发事件 → Controller 调用 Model → Controller 直接修改 View

一句话总结:

Controller 直接协调 Model 和 View。

MVP

View 把事件交给 Presenter → Presenter 调用 Model → Presenter 通过接口命令 View

一句话总结:

Presenter 不直接操作控件,但会命令 View 做什么。

MVVM

View 把输入交给 ViewModel → ViewModel 调用 Model → ViewModel 修改状态 → View 根据状态重新渲染

一句话总结:

ViewModel 不命令 View,只提供 View 需要的状态。

最终可以压缩为三句话:

MVC:Controller 直接修改 View。 MVP:Presenter 通过接口命令 View。 MVVM:ViewModel 修改状态,View 根据状态自己更新。

再进一步压缩:

MVC = 直接协调 MVP = 行为驱动 MVVM = 状态驱动
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 13:42:07

【单片机毕业设计】基于 STM32 传感器数据采集与智能出水控制系统 基于 STM32 的饮水设备安全防护控制系统开发(012101)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/7/30 13:41:59

【单片机毕业设计】基于 STM32F103 的婴儿监护与自动摇床设计 基于声光传感器的婴儿智能看护系统开发(012201)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/7/30 13:41:21

跨境电商税务稽查完整应对流程,收到预警后如何自查、举证、沟通

一、税务预警的几类常见触发原因 系统自动预警全部来源于数据比对差异&#xff0c;常见诱因&#xff1a; 1.平台申报销售额与企业账面收入差额过大&#xff1b; 2.海关出口报关货值、企业申报收入不匹配&#xff1b; 3.法人、股东大额私卡流水无对应经营申报记录&#xff1b; 4…

作者头像 李华
网站建设 2026/7/30 13:41:14

3种字幕搜索引擎集成方案:SubFinder跨平台自动化字幕匹配工具详解

3种字幕搜索引擎集成方案&#xff1a;SubFinder跨平台自动化字幕匹配工具详解 【免费下载链接】subfinder 字幕查找器 项目地址: https://gitcode.com/gh_mirrors/subfi/subfinder 对于影视爱好者和内容创作者而言&#xff0c;视频字幕的获取与匹配一直是个技术门槛较高…

作者头像 李华
网站建设 2026/7/30 13:35:14

# 软考软件设计师题目总结 > **生成时间**: 2026年7月30日 11:12 |

软考软件设计师题目总结生成时间: 2026年7月30日 11:12 | * 距下半年考试: 约86天&#xff08;10月24-27日&#xff09; 本期主题: 软件测试与质量保证专题突破 数据结构算法强化&#xff08;树与图&#xff09; 设计模式代码实战 计算机组成原理核心计算一、2026下半年考情…

作者头像 李华
网站建设 2026/7/30 13:31:36

Spring Boot+Vue健身房管理系统开发实践

1. 项目概述健身房管理系统是一个典型的B/S架构应用&#xff0c;采用前后端分离设计模式。后端基于Spring Boot框架构建&#xff0c;前端使用Vue.js实现&#xff0c;数据库选用MySQL作为持久化存储方案。这套系统主要解决传统健身房手工记录会员信息、课程安排、设备管理等低效…

作者头像 李华