【鸿蒙原生应用开发实战】第五篇:项目总结------ArkTS 最佳实践与从 MVP 到生产的升级之路
历时四篇,我们完整实现了"萌宠日记"鸿蒙原生应用的全部五个页面。这一篇作为系列收官,不做新功能开发,而是回头看------总结整个项目中的架构决策、踩过的坑、ArkTS 严格模式下的编码规范,以及从 MVP 到生产环境的升级路线图。
一、项目架构回顾
1.1 页面总览
pages/
├── Index.ets ← 首页(宠物卡片 + 快捷入口 + 动态信息流)
├── AddPetPage.ets ← 添加宠物(表单录入)
├── PetDetailPage.ets ← 宠物详情(三 Tab 切换)
├── AlbumPage.ets ← 萌宠相册(网格/列表 + 筛选)
└── ReminderPage.ets ← 提醒管理(分类统计 + 待办 + 开关)
1.2 各页面代码量统计
| 页面 | 代码行数 | @Builder 方法数 | @State 变量数 | 核心复杂度 |
|---|---|---|---|---|
| Index.ets | 381 | 4 | 3 | ForEach + Scroll 嵌套 |
| AddPetPage.ets | 323 | 7 | 9 | 多类型表单状态管理 |
| PetDetailPage.ets | 428 | 8 | 9 | Tab 切换 + 柱状图 |
| AlbumPage.ets | 234 | 6 | 3 | 网格/列表双模式 |
| ReminderPage.ets | 339 | 7 | 2 | 开关控制 + 分类统计 |
合计 1705 行 ArkTS 代码,覆盖了鸿蒙原生开发的绝大多数核心场景。
1.3 路由拓扑
Index (首页)
├── pushUrl → AddPetPage (添加宠物)
├── pushUrl → PetDetailPage (宠物详情)
├── pushUrl → AlbumPage (相册)
└── pushUrl → ReminderPage (提醒)
PetDetailPage
└── pushUrl → ReminderPage (提醒设置)
所有页面都是单向跳转,没有循环依赖。router.back() 统一返回上一页。
二、核心设计模式总结
2.1 @Builder 组件化拆分
这是本项目中最核心的架构模式。每个页面将 UI 拆分为多个 @Builder 方法,然后在 build() 中按顺序组装。
为什么不用自定义 @Component?
| 方案 | 适用场景 | 本项目的选择 |
|---|---|---|
@Builder 方法 |
页面内拆分,逻辑简单 | ✅ 首页4个、详情页8个 |
自定义 @Component |
跨页面复用,逻辑复杂 | ❌ 暂不需要 |
全局 @Builder 函数 |
全局通用 UI | ❌ 暂不需要 |
决策原则 :如果一段 UI 只在一个页面内使用 ,就用 @Builder 方法拆分。如果跨页面复用 ,再提取为 @Component。
2.2 状态管理策略
本项目的状态都在页面级别 ,没有跨页面共享状态。每个页面的 @State 数量控制在 10 个以内:
typescript
// 典型模式
struct Index {
@State pets: Pet[] = [];
@State moments: Moment[] = [];
@State selectedPetIndex: number = 0;
}
如果需要跨页面共享状态(比如添加宠物后首页需要刷新),后续可以引入:
@Provide / @Consume:父子组件跨层级传值- AppStorage / LocalStorage:应用级/页面级状态存储
- Emitter:事件总线模式
但目前 MVP 阶段,页面独立管理状态是最简单的方案。
2.3 数据驱动 UI
整个项目遵循"状态变化 → UI 自动更新"的响应式模式:
用户交互 → @State 变量变化 → UI 自动重新渲染
↑ |
└────── onChange/onClick ──────┘
不需要手动操作 DOM,不需要调用 setState(),不需要标记脏更新。这是 ArkTS 声明式框架的核心优势。
三、ArkTS 严格模式踩坑全记录
3.1 arkts-no-untyped-obj-literals
错误信息:
Object literals cannot be used without a type annotation.
问题代码:
typescript
// ❌ 错误
const params = router.getParams();
解决方案:
typescript
// ✅ 正确
const params: Record<string, Object> = router.getParams() as Record<string, Object>;
根因 :ArkTS 严格模式禁止使用无类型标注的对象字面量。从 router.getParams() 返回的 Object 必须显式转换。
3.2 arkts-no-noninferrable-arr-literals
错误信息:
Type of array literal cannot be inferred.
问题代码:
typescript
// ❌ 错误
tabs = ['健康档案', '体重记录', '疫苗记录'];
解决方案:
typescript
// ✅ 正确
tabs: string[] = ['健康档案', '体重记录', '疫苗记录'];
根因:数组字面量必须显式声明类型。这是 ArkTS 为了运行时性能做的类型约束。
3.3 app_name 重复定义
错误信息:
Resource 'app_name' conflict: duplicate definition in multiple modules.
解决方案 :只在 AppScope/resources/base/element/string.json 中定义,不要在 entry 模块中再定义一次。
3.4 router 导入路径
错误信息:
Cannot find module '@kit.AbilityKit' or its corresponding type declarations.
解决方案:
typescript
// ✅ 正确(API 23)
import router from '@ohos.router';
// ❌ 错误(API 23 不导出 router)
import router from '@kit.AbilityKit';
根因 :API 23 的 @kit.AbilityKit 不导出 router。这是 SDK 版本的差异。
3.5 ForEach key 冲突
潜在 Bug :当同一个页面有多个 ForEach 且数据源的 id 范围重叠时,key 可能冲突。
解决方案:
typescript
// 为不同的 ForEach 添加区分后缀
(r: Reminder) => r.id.toString() + 'today'
(reminder: Reminder) => reminder.id.toString()
四、性能优化建议
4.1 列表渲染优化
当前项目中,所有数据都是一次性加载到内存中。当数据量增大时(比如 1000+ 条动态),需要考虑:
| 优化手段 | 说明 | 实施难度 |
|---|---|---|
| 懒加载 | 分页加载,初始只加载前20条 | 低 |
| List 组件 | 替代 Scroll + ForEach,支持回收复用 | 低 |
| LazyForEach | 数据懒加载 + 按需渲染 | 中 |
| 数据不可变 | 整体替换数组而非增删元素 | 低 |
4.2 @Builder 的实例化开销
每次调用 @Builder 方法都会创建新的组件实例。对于频繁切换的 UI(如 Tab 切换),应该用 if-else 避免同时渲染所有内容:
typescript
// ✅ 只渲染当前 Tab,切换时销毁旧 Tab
if (this.currentTab === 0) {
this.buildHealthTab()
} else if (this.currentTab === 1) {
this.buildWeightTab()
} else {
this.buildVaccineTab()
}
而不是:
typescript
// ❌ 所有 Tab 同时渲染(浪费性能)
Column() { this.buildHealthTab() }.visibility(...)
Column() { this.buildWeightTab() }.visibility(...)
Column() { this.buildVaccineTab() }.visibility(...)
4.3 避免不必要的状态更新
@State 变量的每次赋值都会触发 UI 重新渲染。以下写法会造成不必要的性能浪费:
typescript
// ❌ 不必要的中间状态
this.isLoading = true; // 触发一次渲染
this.data = fetchData(); // 触发第二次渲染
this.isLoading = false; // 触发第三次渲染
// ✅ 合并状态更新
this.data = fetchData();
this.isLoading = false;
4.4 图片资源优化
虽然本项目使用 Emoji 替代了图片,但如果后续接入真实图片,需要注意:
- 图片使用
Image组件时设置objectFit(ImageFit.Cover) - 列表中的图片使用
layoutWeight约束尺寸,避免大图溢出 - 图片文件放在
resources/base/media/目录,使用$media引用
五、从 MVP 到生产环境的升级路线图
5.1 第一阶段:功能完善(当前 MVP)
- ✅ 5 个页面完整 UI
- ✅ 页面路由跳转
- ✅ 宠物数据展示
- ✅ 表单录入界面
- ✅ 相册筛选 + 双模式
- ✅ 提醒管理 + 开关控制
5.2 第二阶段:数据持久化
当前所有数据都是硬编码的,重启应用数据就会丢失。生产环境需要:
| 需求 | 技术方案 | 优先级 |
|---|---|---|
| 本地存储 | @ohos.data.preferences (轻量 KV) |
⭐⭐⭐⭐⭐ |
| 结构化数据 | @ohos.data.relationalStore (RDB) |
⭐⭐⭐⭐ |
| 文件存储 | @ohos.file.fs (图片文件) |
⭐⭐⭐ |
| 云同步 | 接入华为云服务 | ⭐⭐ |
推荐路径 :先用 preferences 存宠物基本信息,用 relationalStore 存动态和提醒数据。
5.3 第三阶段:真实功能对接
| 功能 | 待接入 API | 说明 |
|---|---|---|
| 拍照上传 | @ohos.multimedia.camera |
调用系统相机 |
| 相册选择 | @ohos.file.picker |
选择系统相册图片 |
| 推送通知 | @ohos.notification |
疫苗/驱虫到期提醒 |
| 日历同步 | @ohos.calendar |
提醒同步到系统日历 |
| 分享 | @ohos.share |
分享萌宠动态 |
5.4 第四阶段:体验优化
- 骨架屏:数据加载中的占位 UI
- 下拉刷新 :
Swiper+Refresh组件 - 主题切换:暗色模式支持
- 动画过渡:页面转场动画、列表项入场动画
- 无障碍:内容描述、大字体适配
六、开发工具与调试经验
6.1 DevEco Studio 实用技巧
- 预览器 Previewer:实时预览 UI 变化,不用每次跑真机
- HiLog 日志:替代 console.log,支持分级过滤
- Profiler 性能工具:检测 UI 渲染帧率
- 代码格式化 :
Ctrl+Alt+L一键格式化
6.2 命令行构建
bash
node "D:\DevEco Studio\tools\node\node.exe" \
"D:\DevEco Studio\tools\hvigor\bin\hvigorw.js" \
--mode module \
-p module=entry@default \
-p product=default \
-p requiredDeviceType=phone \
assembleHap \
--analyze=normal \
--parallel \
--incremental \
--daemon
构建产物是 .hap 文件,位于 entry/build/default/outputs/ 目录。
6.3 调试建议
- 真机调试优先:模拟器在某些 API 行为上和真机有差异
- 分步构建 :先
Build → Analyze检查代码问题,再Run - 关注编译警告:很多运行期 Bug 在编译期就有警告提示
七、写给初学者的话
7.1 入坑鸿蒙开发需要什么基础?
| 前置技能 | 重要程度 | 说明 |
|---|---|---|
| TypeScript 基础 | ⭐⭐⭐⭐⭐ | ArkTS 是 TypeScript 的子集 |
| 移动端布局思维 | ⭐⭐⭐⭐ | Flexbox 布局概念 |
| 响应式编程理解 | ⭐⭐⭐ | @State 驱动 UI 更新 |
| Java/Android 经验 | ⭐⭐ | 有最好,没有也没关系 |
7.2 学习路线建议
- 先看官方 Codelab:华为官方提供 Hello World 级别的案例
- 手写一个小项目:就像我们这个"萌宠日记"一样,从简单到复杂
- 理解严格模式:ArkTS 的 strict 模式是最大的"坑",也是最大的"保护"
- 多看 build 日志:70% 的错误都在编译期暴露,认真阅读错误信息
7.3 心态建议
鸿蒙开发目前还在快速发展期,API 在不同版本之间可能有差异。这既是挑战也是机会:
- 挑战:网上资料不如 iOS/Android 丰富,遇到问题可能需要自己啃官方文档
- 机会:竞争小,现在入局鸿蒙开发的人少,早起的鸟儿有虫吃
八、项目完整代码汇总
8.1 配置文件
| 文件 | 路径 | 作用 |
|---|---|---|
app.json5 |
AppScope/app.json5 |
应用全局配置 |
module.json5 |
entry/src/main/module.json5 |
模块配置 |
build-profile.json5 |
项目根目录 | 构建配置 |
main_pages.json |
resources/base/profile/ |
页面路由注册 |
color.json |
resources/base/element/ |
颜色资源 |
float.json |
resources/base/element/ |
字号/尺寸 |
string.json |
AppScope/resources/base/element/ |
应用名 |
8.2 页面文件
| 文件 | 路径 | 行数 |
|---|---|---|
| Index.ets | entry/src/main/ets/pages/ |
381 |
| AddPetPage.ets | entry/src/main/ets/pages/ |
323 |
| PetDetailPage.ets | entry/src/main/ets/pages/ |
428 |
| AlbumPage.ets | entry/src/main/ets/pages/ |
234 |
| ReminderPage.ets | entry/src/main/ets/pages/ |
339 |
8.3 Ability 入口
| 文件 | 路径 | 作用 |
|---|---|---|
| EntryAbility.ets | entry/src/main/ets/entryability/ |
应用入口,加载首页 |

九、写在最后
五篇博文,从项目初始化到全部页面实现,我们完整走了一遍鸿蒙原生应用(Stage 模型 + ArkTS + API 23)的开发流程。
复盘整个"萌宠日记"项目,我认为最值得记住的几点:
- ArkTS 的严格模式是把双刃剑------写的时候觉得很繁琐,但编译期捕获了大量潜在 Bug
@Builder+@State是 ArkTS 开发的基石------理解这两个概念,就能看懂 90% 的鸿蒙页面代码- 从 MVP 开始,逐步迭代------不要一开始就想做完美的架构,先跑起来再说
如果你跟着这个系列一起做了一个自己的鸿蒙应用,那我的目的就达到了。有任何问题欢迎评论区交流!
开发环境 : DevEco Studio + HarmonyOS API 23 (SDK 6.1)
框架 : Stage 模型 + ArkTS
系列汇总: