一、先看全局:工程级三层架构
官方推荐将整个工程分为三个层级,各层职责分明、依赖单向:
MyApp/ ← 工程根目录
│
├── products/ ← ① 产品定制层(入口模块)
│ └── phone-entry/ ← 手机端 Entry HAP
│
├── features/ ← ② 基础特性层(业务模块)
│ ├── home/ ← 首页特性 HSP
│ ├── message/ ← 消息特性 HSP
│ └── mine/ ← 我的特性 HSP
│
└── common/ ← ③ 公共能力层(基础设施)
├── uicomponents/ ← 公共 UI 组件 HSP
├── network/ ← 网络封装 HSP
└── utils/ ← 工具类 HSP
三层职责
| 层级 | 目录名 | 职责 | 典型内容 |
|---|---|---|---|
| 产品定制层 | products/ | 应用入口,决定"长什么样、给谁用" | Entry HAP、设备适配 UI、入口页面、专属资源 |
| 基础特性层 | features/ | 独立业务功能,决定"能做什么" | 首页、消息、购物车、支付等独立功能模块 |
| 公共能力层 | common/ | 底层基础支撑,决定"靠什么能力" | 公共 UI 组件、网络封装、工具类、数据管理 |
铁律:依赖单向向下
✅ 允许的依赖:
products → features
products → common
features → common
❌ 禁止的依赖:
common → features (底座不能依赖上层)
products → 跳过 features 直接依赖其内部实现
home → message (同层不互相依赖)
二、再看内部:模块内 MVVM 分层
每个模块(无论 HAP、HAR 还是 HSP)内部,推荐按「功能 + MVVM」组织,按功能分目录,不在同一层级混放所有 Model:
features/cart/ ← 一个功能模块
├── src/main/ets/
│ ├── model/ ← Model 层:纯数据
│ │ ├── CartItem.ets ← 购物车单品模型
│ │ ├── CartSummary.ets ← 结算汇总模型
│ │ └── OrderInfo.ets ← 订单模型
│ │
│ ├── viewmodel/ ← ViewModel 层:业务逻辑
│ │ ├── CartViewModel.ets ← 购物车业务逻辑
│ │ └── CheckoutViewModel.ets ← 结算业务逻辑
│ │
│ ├── view/ ← View 层:UI 页面与组件
│ │ ├── CartPage.ets ← 购物车主页面
│ │ ├── CartItemCard.ets ← 单条商品卡片
│ │ └── CartBottomBar.ets ← 底部结算栏
│ │
│ ├── index.ets ← 模块对外统一出口
│ └── pages/ ← 模块对外暴露的页面
│
├── resources/ ← 模块专属资源
├── module.json5 ← 模块配置
└── oh-package.json5 ← 模块依赖声明
MVVM 三层职责边界
┌─────────────────────────────────────────────┐
│ View (@ComponentV2) │
│ - 只做页面渲染和事件回调 │
│ - 不写网络请求、不处理复杂业务判断 │
│ - 通过 @Param 接收数据,通过 @Event 上报事件 │
└──────────────┬──────────────────────────────┘
│ 事件回调 ↑ ↓ 数据绑定
┌──────────────┴──────────────────────────────┐
│ ViewModel (@ObservedV2) │
│ - 持有 Model,处理所有业务逻辑 │
│ - 接口调用、状态管理、数据转换 │
│ - 加载/错误/空状态管理 │
└──────────────┬──────────────────────────────┘
│ 读写
┌──────────────┴──────────────────────────────┐
│ Model (@ObservedV2 + @Trace) │
│ - 纯数据结构,不依赖 UI │
│ - @Trace 字段变化自动通知 View 刷新 │
│ - 可包含 @Computed 计算属性 │
└─────────────────────────────────────────────┘
三、完整落地示例
整体目录(中型项目)
MyApp/
├── AppScope/ ← 应用全局配置
│ └── app.json5 ← 应用包名、版本、图标
│
├── products/ ← 产品定制层
│ └── phone-entry/ ← 手机端入口 Entry HAP
│ ├── src/main/ets/
│ │ ├── entryability/ ← UIAbility 入口
│ │ ├── pages/ ← 入口页面
│ │ └── ...
│ ├── resources/
│ └── module.json5
│
├── features/ ← 基础特性层
│ ├── home/ ← 首页 HSP
│ │ ├── src/main/ets/
│ │ │ ├── model/ ← 首页数据模型
│ │ │ ├── viewmodel/ ← 首页业务逻辑
│ │ │ ├── view/ ← 首页 UI
│ │ │ ├── pages/ ← 首页对外页面
│ │ │ └── index.ets ← 统一导出
│ │ └── ...
│ ├── search/ ← 搜索 HSP
│ │ └── src/main/ets/
│ │ ├── model/
│ │ ├── viewmodel/
│ │ └── view/
│ └── mine/ ← 我的 HSP
│ └── src/main/ets/
│ ├── model/
│ ├── viewmodel/
│ └── view/
│
├── common/ ← 公共能力层
│ ├── uicomponents/ ← 公共 UI 组件 HSP
│ │ └── src/main/ets/
│ │ ├── components/ ← 按钮、卡片等
│ │ └── index.ets
│ ├── network/ ← 网络封装 HSP
│ │ └── src/main/ets/
│ │ ├── HttpClient.ets ← 请求封装
│ │ ├── ApiService.ets ← 接口定义
│ │ └── index.ets
│ └── utils/ ← 工具类 HSP
│ └── src/main/ets/
│ ├── DateUtils.ets
│ ├── StringUtils.ets
│ └── index.ets
│
├── hvigor/ ← 构建配置
├── oh-package.json5 ← 工程级依赖
└── build-profile.json5 ← 构建配置
模块对外暴露:index.ets 统一出口
每个模块通过 index.ets 控制对外暴露的接口,上层模块通过 oh-package.json5 声明依赖:
// common/uicomponents/index.ets — 公共组件层统一出口
export { SectionCard } from './src/main/ets/components/SectionCard'
export { LoadingView } from './src/main/ets/components/LoadingView'
export { EmptyView } from './src/main/ets/components/EmptyView'
// features/cart/index.ets — 购物车模块统一出口
export { CartViewModel } from './src/main/ets/viewmodel/CartViewModel'
export { CartPage } from './src/main/ets/view/CartPage'
// 不导出内部实现细节:CartItemCard、CartBottomBar
// features/home/oh-package.json5 — 首页模块的依赖声明
{
"dependencies": {
"@hm/common": "file:../../common" // 只依赖公共层,不依赖同层的 cart
}
}
四、不同规模项目的目录策略
🟢 小型项目(3~5 个页面,单人开发)
不需要拆 HAR/HSP 模块,在单 Entry 内按 MVVM 分层即可:
entry/src/main/ets/
├── pages/ ← 页面(View)
│ ├── IndexPage.ets
│ ├── DetailPage.ets
│ └── SettingPage.ets
├── viewmodel/ ← 业务逻辑(ViewModel)
│ ├── IndexViewModel.ets
│ └── DetailViewModel.ets
├── model/ ← 数据模型(Model)
│ ├── ArticleItem.ets
│ └── UserInfo.ets
├── common/ ← 公共工具
│ ├── http/
│ ├── utils/
│ └── components/
└── entryability/
🟡 中型项目(10~20 个页面,团队协作)
引入模块化,按功能拆分 HAR 或 HSP:
entry/src/main/ets/ ← 主入口,薄薄一层
├── pages/
└── entryability/
features/
├── home/ (HSP) ← 首页模块
├── discover/ (HSP) ← 发现模块
├── cart/ (HSP) ← 购物车模块
└── mine/ (HSP) ← 个人中心模块
common/
├── uicomponents/ (HSP) ← 公共 UI
├── network/ (HSP) ← 网络封装
└── utils/ (HSP) ← 工具类
🔴 大型项目(多端 + 多团队)
全面启用三层架构 + Feature HAP 按需下载:
products/
├── phone-entry/ (Entry HAP)
├── tablet-entry/ (Entry HAP)
└── tv-entry/ (Entry HAP)
features/
├── home/ (HSP — 各端共用)
├── live/ (Feature HAP — 仅 TV 端按需下载)
├── payment/ (HSP)
└── chat/ (Feature HAP — 按需下载,deliveryWithInstall: false)
common/
├── uicomponents/ (HSP)
├── network/ (HSP)
├── database/ (HSP)
└── analytics/ (HSP)
五、常见反模式(务必避免)
| 反模式 | 问题 | 正确做法 |
|---|---|---|
models/、viewmodels/、views/ 三个大目录平铺所有文件 | 改一个功能要在三个目录间反复跳,模块边界模糊 | 按功能模块分目录,每个模块内部再分 model/viewmodel/view |
公共层 common 里写了业务逻辑 | 底座与业务耦合,换个项目用不了 | common 只放无业务属性的基础设施 |
| 页面里直接写网络请求和数据解析 | 代码不可测试、不可复用 | 网络请求放 ViewModel,页面只负责渲染 |
| 同层模块互相 import | 形成依赖环,编译可能报错,逻辑耦合 | 共享部分下沉到 common |
| 一个 Entry 里堆了所有业务代码 | 无法做按需下载,编译慢,团队冲突大 | 按功能拆成 HAR/HSP 模块 |
六、总结:一张图记住
工程目录 = 三层架构(横向) × MVVM 分层(纵向)
┌──────────────────┐
│ products/ │ ← 入口 + 设备适配
│ (Entry HAP) │
├──────────────────┤
依赖单向向下 → │ features/ │ ← 独立业务模块
│ (HSP / HAR) │
├──────────────────┤
│ common/ │ ← 公共基础设施
│ (HSP / HAR) │
└──────────────────┘
每个模块内部 = model/ + viewmodel/ + view/ + index.ets
记住两个原则:
- 横向分层:products → features → common,依赖只向下。
- 纵向分块:每个 feature 内部 model / viewmodel / view 各司其职,通过 index.ets 对外暴露。