我把一个从“附近停车场查询”到“预约车位”的工具型App用 ArkTS 完整做了一遍,真机跑通之后最大的感受是:难的不是语法,而是整个思维切换。用惯了 Android 的 XML/Compose 或 iOS 的 UIKit/SwiftUI 再来看鸿蒙开发,先把 ArkTS 那套声明式 UI 和状态管理逻辑接受下来,后面会顺很多。如果你正准备用 ArkTS 做实际业务,这篇就按我真实做停车应用的过程来拆:项目建好后怎么分模块、首页列表怎么做、定位和地图怎么接、网络层怎么封、预约状态机怎么设计,最后再聊模拟器和真机上那些最容易被忽略的坑。
1. 先把停车业务压缩成可落地的MVP:ArkTS不是瓶颈,需求才是
1.1 我最终决定做的功能集合
很多人在拿到“停车应用”这个题目时,第一反应是“先做一个地图,能看到附近停车场”。这其实是最危险的起点。地图只是展示手段,停车业务的核心是“车位状态在时间上的变化”:哪个停车场还有空位、哪个车位被预约了、预约后多久未到场会释放。如果你连业务对象都没理清,一上来就接地图SDK,后面接口一变,页面状态全得返工。
我最后把功能收敛成了这三个主流程:
- 停车场列表:按当前位置展示附近停车场,显示距离、总车位数、剩余车位数、收费标准。
- 停车场详情:展示车场楼层/区域结构、实时空位数量、收费标准明细,支持“预约车位”入口。
- 预约与状态流转:用户选择可预约时段,创建订单,生成预约倒计时;到达后可确认入场;超时未到场自动取消。
没有第一版就做支付、月卡、会员、停车记录等。因为这些功能会把业务复杂度拉高一个量级,但对验证 ArkTS 方案来说不是必需品。
1.2 数据模型和接口边界先于页面定义
第一次用 ArkTS 写业务时,我很自然地照着 TypeScript 的习惯先写类型。停车应用里至少有这几个核心模型:
typescript复制export class ParkingLot {
id: string;
name: string;
address: string;
latitude: number;
longitude: number;
totalSpace: number;
freeSpace: number;
pricePerHour: number;
distance?: number;
}
export class ReservationOrder {
orderId: string;
lotId: string;
spaceCode: string;
status: 'CREATED' | 'RESERVED' | 'ARRIVED' | 'EXPIRED' | 'CANCELED';
reserveStart: number;
reserveEnd: number;
}
我建议把类属性定义得比页面字段多一些,因为接口返回的字段往往有下划线风格,后端可能需要 lot_id,前端需要 lotId。在网络层统一做一次字段映射,页面层直接使用干净的驼峰模型,能省掉很多麻烦。
1.3 “纯鸿蒙”带来的隐性差异
用 ArkTS 时,不要把它当成标准 TypeScript 的子集来写。官方对动态类型有很多限制,比如在普通工程里不建议用 any、不能用某些 JS 运行时特性,代码需要被 ArkTS 编译器约束。这对写业务来说其实是好事,它倒逼你把“数据是什么”定义清楚。
我实际写下来最大的差异来自 UI 框架:ArkUI 不是传统 Controller 模式,而是用 @Component 定义组件、用 build() 描述界面。页面的主体不是 View 的继承体系,而是一个个 struct。一开始很不习惯,因为布局、事件、状态全挤在一个结构体里,如果不刻意控制粒度,一个页面文件很容易写到上千行。
所以我的第一建议是:不要等到写页面时才想架构。先确认需求边界,再开始做 ArkTS 工程,这是整个项目省时间的前提。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 新建工程与模块布局:entry、feature、HAR的角色别混淆
2.1 DevEco Studio建工程时的几个关键选择
我用 DevEco Studio 新建工程时,目标设备选了 Phone,语言选了 ArkTS,工程模板直接用了 Empty Ability。这里要注意,模板默认只生成一个 entry 模块,很多教程也默认所有页面都扔在 entry 里。如果只是写 demo,这没问题;但一旦页面超过五个,公共工具类、网络层、数据模型全部堆在 entry 里,后续改一处就要全量编译,效率会明显下降。
建议新建工程之后,按这样的模块边界来调整:
| 模块 | 类型 | 职责 |
|---|---|---|
| AppScope | 全局配置 | 应用级配置、公共资源 |
| entry | 应用入口模块 | 启动页、首页框架、应用生命周期 |
| common | HAR | 网络请求、通用工具、基础数据模型 |
| feature-parking | HAR/feature | 停车业务相关页面与状态管理 |
| service-parking | HAR | 停车业务仓库层,封装接口调用 |
我不是让你一上来就把模块拆得很碎。停车这种中小型项目,拆一层 common 公共 HAR,再拆一个 feature-parking 业务模块就够了。核心原则是:纯技术能力下沉到 HAR,业务页面按功能域隔离,入口模块只负责装配和启动。
2.2 HAR里能不能放so?封装公共能力时的注意点
关键词里出现“鸿蒙HAR封装so”,这确实是实际工程里会遇到的问题。如果停车应用里要复用一套已经编译好的 C/C++ 能力,比如车牌识别、停车场道闸计费算法,通常做法是把带 N-API 接口的代码打成 HAR 包,然后通过 oh-package.json5 的本地依赖引入。
json复制{
"dependencies": {
"common": "file:../common",
"feature-parking": "file:../feature-parking"
}
}
这里有个容易踩的坑:HAR 里如果包含 .so 文件,不要通过页面里的相对路径去引,ArkTS 的模块解析对这种方式很敏感。正确的做法是让 HAR 导出接口声明文件(index.d.ts),页面代码只 import 导出的函数或类。如果项目编译后出现“找不到 so 文件”或“loader returne
