前言
代码架构的出现,不仅仅是为了让代码之间结构清晰,便于维护,更是为了软件质量做底层加固,虽然说,所有的网络请求,所有的业务逻辑,我们都可以放到UI页面里来处理,在用户视觉的效果里没有什么问题,但是在开发层,长此以往,暴露的问题则是越积越深,比如页面代码臃肿,毫无代码复用可言,维护成本剧增等等问题,所以,没有架构的项目,就像一堆随意堆放的砖头和钢筋,虽然材料齐全,但风一吹就倒。
代码架构,在一个成熟且完整的项目中,是必不可少的,具体来说,架构解决了三个根本痛点:
第一、应对需求的频繁变化:产品经理今天要改UI样式,明天要换第三方SDK。如果没有分层,改一个按钮颜色可能会牵连到数据逻辑,改一个接口地址可能导致整个页面崩溃。架构提供了"隔离带",让变化只影响局部。
第二、降低认知负荷:人脑同时能处理的信息有限。如果没有分层,一个文件里既写网络请求、又写数据处理、还写UI布局,几百行代码混在一起,开发者大脑会"过载"。架构通过"关注点分离",让你看View时只关心UI,看Model时只关心数据。
第三、支持团队并行开发:在团队协作中,各业务模块相互对立,各司其职。如果没有架构,大家会在同一个文件里"打架"(合并冲突)。架构定义了清晰的契约(接口),让分工成为可能。
目前主流的代码架构就是MVVM了,可以说是很多语言或者三方架构的首选,像Android,IOS,Vue、小程序,等等,几乎都是主推MVVM,当然了鸿蒙也不例外,官方的ArkUI采用了Model-View-ViewModel(MVVM)架构模式。
什么是MVVM?
MVVM将应用分为Model、View和ViewModel三个核心部分,实现数据、视图与逻辑的分离。通过这种模式,UI可以随着状态的变化自动更新,无需手动处理,从而高效管理数据和视图的绑定与更新。
Model:存储和管理应用数据及业务逻辑,不直接与用户界面交互。通常从后端接口获取数据,是应用程序的数据基础,确保数据的一致性和完整性。
View:负责用户界面展示数据并与用户交互,不包含任何业务逻辑。它通过绑定ViewModel层提供的数据来动态更新UI。
ViewModel:负责管理UI状态和交互逻辑。作为连接Model和View的桥梁,ViewModel监控Model数据的变化,通知View更新UI,同时处理用户交互事件并转换为数据操作。

登录案例
我们就用一个登录案例,来完整的展示鸿蒙当中的mvvm架构模式。
model层
model层包含两部分,一部分是实体类对象,也就是登录之后,把接口返回的数据转换为实际开发中需要的用户数据,可能包含了,用户的ID,头像,昵称等等数据。
以下为测试数据,实际开发中,请以具体业务为主。
TypeScript
/**
*AUTHOR:AbnerMing
*DATE:2026/7/23
*INTRODUCE:登录model
*/
export class LoginModel {
/**
*AUTHOR:AbnerMing
*INTRODUCE:发起登录
*/
login(userName: string, userPass: string): UserBean {
//在实际开发中,进行执行网络请求,把服务器返回的json数据,进行存储或者转换为用户对象
//这里为简单模拟数据返回
let bean = new UserBean()
bean.userName = userName
bean.userPass = userPass
return bean
}
}
/**
*AUTHOR:AbnerMing
*INTRODUCE:模拟的数据对象
*/
export class UserBean {
userName?: string
userPass?: string
}
viewmodel层
TypeScript
import { UserBean, LoginModel } from "../model/LoginModel";
/**
*AUTHOR:AbnerMing
*DATE:2026/7/23
*INTRODUCE:处理用户登录相关逻辑,存储用户信息数据,判断用户是否登录等
*/
@ObservedV2
export class LoginViewModel {
@Trace userBean?: UserBean
/**
*AUTHOR:AbnerMing
*INTRODUCE:执行登录
*/
login(userName: string, userPass: string) {
let model = new LoginModel()
this.userBean = model.login(userName, userPass)
}
}
view层
TypeScript
import { LoginViewModel } from '../viewmodel/LoginViewModel'
/**
*AUTHOR:AbnerMing
*DATE:2026/7/23
*INTRODUCE:登录
*/
@Entry
@ComponentV2
struct LoginPage {
@Local userName?: string
@Local userPass?: string
loginViewModel: LoginViewModel = new LoginViewModel()
build() {
Column() {
TextInput({ placeholder: "请输入账号", text: $$this.userName })
TextInput({ placeholder: "请输入密码", text: $$this.userPass })
.margin({ top: 10 })
Button("登录")
.margin({ top: 10 })
.onClick(() => {
//发起登录
if (this.userName == undefined || this.userPass == undefined) {
this.getUIContext().getPromptAction().showToast({ message: "请输入账号或密码" })
return
}
//执行登录逻辑
this.loginViewModel.login(this.userName, this.userPass)
})
Text("测试渲染用户信息:账号:" + this.loginViewModel.userBean?.userName + " 密码" +
this.loginViewModel.userBean?.userPass)
.margin({ top: 10 })
}.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.padding({ left: 20, right: 20 })
}
}
以上就是一个很简单的登录逻辑,由view层触发viewmodel层的登录操作,再由viewmodel层执行model层的登录请求,最后把数据返回给viewmodel层,由viewmodel层通知View更新UI。

程序包创建
mvvm并不是一成不变的,在实际的开发中,可能会有一个业务一组mvvm包,比如登录是一个业务,注册是一个业务,视频是一个业务,那么每个业务下,都会存在model、view、viewmodel三个包,这是为了便于业务结构清晰,当然也有着同模块下只有model、view、viewmodel三个包,所有的业务按照类型存在着不同的包下,这种方式减少了程序包的创建。
两种方式哪种方式好呢?对于多业务模块的项目,推荐按业务模块划分,即每个业务模块内部各自包含独立的view、model、viewmodel子包,而非全局只建一套view/model/viewmodel。这种"高内聚、低耦合"的组织方式在模块扩展、团队协作和后续拆分为HAR/HSP动态包时都更具优势。
两种方案对比


工程目录结构
TypeScript
entry/ # 主入口模块
├── src/main/ets/
│ ├── entryability/ # Ability入口
│ ├── pages/ # 页面路由入口(仅引用各模块的View)
│ │
│ ├── features/ # 业务模块集合
│ │ ├── login/ # 登录模块
│ │ │ ├── view/ # 登录页面组件
│ │ │ │ ├── LoginPage.ets
│ │ │ │ └── LoginComponents.ets
│ │ │ ├── viewmodel/ # 登录状态与逻辑
│ │ │ │ └── LoginViewModel.ets
│ │ │ └── model/ # 登录数据接口与模型
│ │ │ ├── LoginRepository.ets
│ │ │ └── LoginTypes.ets
│ │ │
│ │ ├── register/ # 注册模块
│ │ │ ├── view/
│ │ │ │ └── RegisterPage.ets
│ │ │ ├── viewmodel/
│ │ │ │ └── RegisterViewModel.ets
│ │ │ └── model/
│ │ │ ├── RegisterRepository.ets
│ │ │ └── RegisterTypes.ets
│ │ │
│ │ ├── video/ # 视频模块
│ │ │ ├── view/
│ │ │ │ ├── VideoListPage.ets
│ │ │ │ ├── VideoDetailPage.ets
│ │ │ │ └── components/ # 视频模块专用UI组件
│ │ │ │ └── VideoCard.ets
│ │ │ ├── viewmodel/
│ │ │ │ ├── VideoListViewModel.ets
│ │ │ │ └── VideoDetailViewModel.ets
│ │ │ └── model/
│ │ │ ├── VideoRepository.ets
│ │ │ └── VideoTypes.ets
│ │ │
│ │ └── community/ # 社区模块
│ │ ├── view/
│ │ │ ├── CommunityPage.ets
│ │ │ └── PostDetailPage.ets
│ │ ├── viewmodel/
│ │ │ └── CommunityViewModel.ets
│ │ └── model/
│ │ ├── CommunityRepository.ets
│ │ └── CommunityTypes.ets
│ │
│ ├── common/ # 跨模块公共层
│ │ ├── components/ # 公共UI组件(如按钮、弹窗、 loading)
│ │ ├── utils/ # 工具类(网络请求封装、格式化等)
│ │ ├── constants/ # 全局常量
│ │ └── types/ # 公共类型定义
│ │
│ └── network/ # 网络层(各模块共享的请求基础设施)
│ ├── HttpClient.ets
│ └── ApiResponse.ets
model层拆分
在实际的开发中,我们可能会经常遇到,业务逻辑代码复用的问题,比如很多个页面都需要进行某一个网络请求,或者数据操作,那么这种情况下,我们就可以对model层进行拆分,分为数据实体类,数据源,数据仓库。

Repository层示例:
TypeScript
// models/datasource/UserApi.ets ------ 网络请求的具体实现
export class UserApi {
async fetchUser(userId: string): Promise<User> {
const response = await fetch(`https://api.example.com/users/${userId}`);
return response.json();
}
}
// models/repositories/UserRepository.ets ------ 数据仓库,统一管理数据来源
export class UserRepository {
private api: UserApi = new UserApi();
async getUser(userId: string): Promise<User> {
// 可在此加入缓存策略、错误处理等逻辑
return await this.api.fetchUser(userId);
}
}
ViewModel层示例:
TypeScript
// viewmodels/HomeViewModel.ets
@ObservedV2
export class HomeViewModel {
private repository: UserRepository = new UserRepository();
@Trace userInfo: User | null = null;
async loadUser(userId: string) {
try {
this.userInfo = await this.repository.getUser(userId);
} catch (error) {
// 处理错误,更新UI状态
}
}
}
相关总结
当然了,在实际的开发中,大家结合自己的业务,具体问题具体分析。以上的案例使用的是V2装饰器,如果你还在使用V1,可能在装饰器上有所差异,其他的大致都一样。在开发中通过正确应用Model、View和ViewModel分层的代码结构,可以使得项目在开发中提升开发效率、保证代码质量,并优化数据与UI的同步机制,简化整体开发流程。