当前位置: 首页 > news >正文

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 = 状态驱动
http://www.cnnetsun.cn/news/3749810.html

相关文章:

  • 【单片机毕业设计】基于 STM32 传感器数据采集与智能出水控制系统 基于 STM32 的饮水设备安全防护控制系统开发(012101)
  • 【单片机毕业设计】基于 STM32F103 的婴儿监护与自动摇床设计 基于声光传感器的婴儿智能看护系统开发(012201)
  • 跨境电商税务稽查完整应对流程,收到预警后如何自查、举证、沟通
  • 3种字幕搜索引擎集成方案:SubFinder跨平台自动化字幕匹配工具详解
  • # 软考软件设计师题目总结 > **生成时间**: 2026年7月30日 11:12 |
  • Spring Boot+Vue健身房管理系统开发实践
  • AI赋能不锈钢质检
  • 2026年7月上海电子销毁Top1推荐:赛奈废旧物资回收怎么样?
  • 4步掌握Path of Building:打造流放之路Build规划的终极工具箱
  • 南京江北新区做展厅,把“集成电路“讲清楚就够了
  • FPGA开发入门:从Verilog编程到板级调试的完整实验指南
  • SpringBoot+Vue3构建学校防疫物资管理系统实践
  • 中小卖家发欧洲:别盲目跟风空运,卡航才是性价比之王
  • AI重塑工作方式的底层逻辑(2023全球127家头部企业实证数据首发)
  • 2026年高含金量证书盘点:零基础跨行、提升职场核心竞争力的考证避坑指南
  • Forza-Mods-AIO:突破极限竞速地平线游戏体验的专业级修改工具
  • GetQzonehistory:QQ空间数据归档的技术实现与架构解析
  • 百度网盘提取码智能获取工具:异步架构与分布式查询原理深度解析
  • 别再盲目试用了!AI搜索产品选型决策树来了:5维评估模型(语义理解深度、实时性、溯源可信度、隐私合规性、企业集成能力)一键匹配你的业务场景
  • 好用还专业!盘点2026年碾压级的一键生成论文工具
  • 未来印象案例分享|华为智慧展厅
  • 四方电气 DX500 变频器赋能磁悬浮鼓风机:工业能效升级的技术路径深度解析
  • 10个技巧:用Nuke Survival Toolkit提升合成效率300%
  • Claude Code终极指南:如何用自然语言命令提升3倍编程效率
  • Unity BRG射击游戏开发:从架构设计到AI与性能优化全流程实战
  • 电力市场优化:CPLEX实现可再生能源消纳模型
  • 不要把 Skill 写成 Prompt
  • C++11互斥量与条件变量:构建线程安全队列的实战指南
  • BetterNCM插件管理器完整指南:5分钟解锁网易云音乐无限潜力
  • 【单片机课程设计/毕业设计】基于 STM32 的带锁定保护密码锁硬件设计 基于嵌入式单片机的安防密码开锁系统设计(012501)