鸿蒙 Next 宠物健康日记 App 开发实战:双键持久化 + 时间线 + 系列



鸿蒙 Next 宠物健康日记 App 开发实战:双键持久化 + 时间线 + 系列
作者:DULUO
SDK 版本:HarmonyOS API 24 (Next)
开发工具:DevEco Studio
语言框架:ArkTS + ArkUI
字数:约 11000 字
目录
- 引言
- 产品概念与数据模型
- 三 Tab 架构与宠物列表
- 健康记录时间线
- 双键值持久化策略
- 宠物 CRUD 与表单设计
- 健康贴士知识库
- 紧凑代码风格与错误预防
- 编译错误全记录
- 七款 App 系列总结
- HarmonyOS 开发终章感言
1. 引言
1.1 宠物健康管理的痛点
根据《2023-2024 年中国宠物行业白皮书》,中国城镇宠物数量已超过 1.2 亿只。然而,许多宠物主人在健康管理方面存在以下痛点:
- 疫苗遗忘:不记得上次打疫苗的时间
- 病历散乱:纸质病历容易丢失
- 健康信息分散:不同宠物的信息混在一起
- 缺乏提醒:不知道什么时候该体检
"宠物健康日记"App 正是为了解决这些痛点而设计——集中记录每只宠物的健康事件,形成可视化的时间线。
1.2 本 App 的技术定位
| 维度 | 前六款 | 宠物健康日记 |
|---|---|---|
| 数据模型 | 单表/单键值 | 双表 + 双键值持久化 |
| Builder 风格 | 多行展开 | 紧凑风格(单行链式) |
| 核心交互 | 单数据操作 | 主从数据(宠物 + 事件) |
| 代码组织 | 详细展开 | 高密度(450 行实现全功能) |
1.3 从六到七:系列的演进
回顾七款 App 的发展轨迹:
白噪音 (767行) → 单页 + 多媒体
时间胶囊 (955行) → 单页 + 持久化
冰箱剩菜 (1320行) → Tab 架构 + 游戏化 ← 行数峰值
尴尬粉碎机 (953行) → 模式复用 + 1 个错误 ← 错误谷值
防骗训练 (1038行) → 适老化设计
碎片学习 (851行) → 学习激励
宠物日记 (450行) → 紧凑风格 + 双键持久化 ← 行数谷值
从 1320 行到 450 行,代码量减少了 66%,但功能完整性并未降低——这证明了 ArkUI 模式的成熟和开发效率的提升。
2. 产品概念与数据模型
2.1 功能需求
用户故事 1:我想记录每只宠物的基本信息和健康事件
用户故事 2:我想按时间线查看某只宠物的所有健康记录
用户故事 3:我想学习一些养宠健康知识
功能清单:
├── F1: 添加宠物(头像 + 名字 + 品种 + 出生日期)
├── F2: 宠物列表浏览
├── F3: 查看宠物详情(健康统计)
├── F4: 删除宠物(同时删除关联事件)
├── F5: 添加健康记录(类型 + 标题 + 描述 + 日期)
├── F6: 按宠物切换健康时间线
├── F7: 6 条健康小贴士
└── F8: 双键值数据持久化
2.2 数据模型
interface Pet {
id: number; // 唯一标识
name: string; // 宠物名字
breed: string; // 品种
birthDate: number; // 出生日期时间戳
avatar: string; // Emoji 头像
color: string; // 主题色
notes: string; // 备注
}
interface HealthEvent {
id: number; // 唯一标识
petId: number; // 关联的宠物 ID
date: number; // 事件日期时间戳
type: string; // 事件类型(疫苗/体检/用药/生病/美容/其他)
title: string; // 事件标题
detail: string; // 详细描述
}
为什么用 petId 关联而不是嵌套?
这是数据库设计中的"规范化"原则——将宠物和健康事件分为两个独立的数据集:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 嵌套结构(事件在宠物对象内) | 查询单个宠物的所有事件快 | 删除宠物时需遍历嵌套数组 |
关联结构(通过 petId 关联) |
事件可独立查询和排序 | 需要 filter 操作关联 |
我们选择了关联结构,因为:
- 健康事件需要按时间全局排序(时间线展示)
- 删除宠物时也需要同时删除关联事件
- 分离数据使持久化更灵活(可以分开读写)
2.3 颜色主题与宠物头像
const PET_AVATARS: string[] = ['🐶', '🐱', '🐰', '🐹', '🐦', '🐢', '🐟', '🦜'];
const PET_COLORS: string[] = ['#FF7043', '#42A5F5', '#66BB6A', '#AB47BC', ...];
8 种头像和 8 种颜色一一对应,用户选择头像时自动分配对应颜色。
颜色在 UI 中的运用:
- 卡片边框:
pet.color + '33'(20% 透明度) - 卡片阴影:
pet.color + '10'(6% 透明度) - 选中态背景:
pet.color + '15'(8% 透明度)
2.4 事件类型体系
const EVENT_TYPES: string[] = ['疫苗', '体检', '用药', '生病', '美容', '其他'];
const EVENT_ICONS: string[] = ['💉', '🔬', '💊', '🤒', '💈', '📋'];
六种事件类型,每种有对应的 Emoji 图标和主题色:
| 类型 | 图标 | 颜色 | 场景示例 |
|---|---|---|---|
| 疫苗 | 💉 | #42A5F5 蓝色 |
狂犬疫苗、三联疫苗 |
| 体检 | 🔬 | #66BB6A 绿色 |
年度体检、血检 |
| 用药 | 💊 | #AB47BC 紫色 |
驱虫药、消炎药 |
| 生病 | 🤒 | #EF5350 红色 |
呕吐、拉肚子、皮肤病 |
| 美容 | 💈 | #FFA726 橙色 |
洗澡、剪毛、剪指甲 |
| 其他 | 📋 | #78909C 灰色 |
搬家、旅行、绝育 |
3. 三 Tab 架构与宠物列表
3.1 Tab 配置
@Builder
buildTabContent() {
if (this.activeTab === 0) { this.buildPetList() }
else if (this.activeTab === 1) { this.buildTimeline() }
else { this.buildHealthTips() }
}
3.2 Tab 0:宠物列表
@Builder
buildPetList() {
Column() {
if (this.petList.length === 0) {
// 空状态
} else {
Scroll() {
Column() {
ForEach(this.petList, (pet: Pet) => {
Column() {
Row() {
Text(pet.avatar).fontSize(40)
Column() {
Text(pet.name).fontSize(18).fontWeight(FontWeight.Bold)
Row() { Text(pet.breed); Text(' · '); Text(this.calcAge(pet.birthDate) + '岁') }
}
Text('❯')
}
}
.borderColor(pet.color + '33')
.onClick(() => { this.selectedPet = pet; this.showPetDetail = true; })
})
}
}
}
}
}
3.3 宠物年龄计算
calcAge(birthTs: number): string {
let diff = Date.now() - birthTs;
let years = Math.floor(diff / (365.25 * 24 * 60 * 60 * 1000));
if (years < 1) {
let months = Math.floor(diff / (30.44 * 24 * 60 * 60 * 1000));
return months < 1 ? '1月' : months.toString();
}
return years.toString();
}
使用 365.25(考虑闰年)和 30.44(月平均天数)近似计算,对于宠物年龄这种无需精确到天的场景足够使用。
4. 健康记录时间线
4.1 时间线 UI 结构
● 狂犬疫苗 05/15
│ 宠物医院接种第三针
└── [疫苗]
● 年度体检 03/20
│ 一切正常,体重5.2kg
└── [体检]
● 皮肤病 01/08
│ 腹部出现红斑,已用药
└── [生病]
4.2 实现代码
ForEach(this.events.filter(e => e.petId === this.selectedPet!.id), (evt: HealthEvent) => {
Row() {
// 左侧:时间线圆点 + 连线
Column() {
Circle().width(12).height(12).fill(this.getEventColor(evt.type))
Divider().width(2).height(40).color(C.border + '66')
}
// 右侧:事件内容
Column() {
Row() {
Text(EVENT_ICONS[EVENT_TYPES.indexOf(evt.type)]).fontSize(16)
Text(evt.title).fontSize(15).fontWeight(FontWeight.Bold)
Text(this.formatDate(evt.date)).fontSize(11).fontColor(C.textHint)
}
Text(evt.detail).fontSize(13).fontColor(C.textLight)
Text(evt.type).fontSize(11).fontColor(Color.White)
.backgroundColor(this.getEventColor(evt.type)).borderRadius(8)
}
}
})
4.3 时间线设计要点
- 圆点颜色:根据事件类型变化——蓝色💉、绿色🔬、紫色💊、红色🤒
- 连线:Divider 垂直放置,连接相邻事件
- 类型标签:彩色胶囊标签,一目了然
- 紧凑布局:左侧 12px 圆点 + 40px 连线,右侧文本占满剩余空间
4.4 事件类型颜色映射
getEventColor(type: string): string {
if (type === '疫苗') return C.vaccine;
if (type === '体检') return C.checkup;
if (type === '用药') return C.med;
if (type === '生病') return C.illness;
if (type === '美容') return C.grooming;
return C.other;
}
5. 双键值持久化策略
5.1 为什么分开存储
与前三款 App 的单键值存储不同,本 App 需要管理两组独立数据:
| 键名 | 存储内容 | 更新频率 | 数据量级 |
|---|---|---|---|
pet_data |
Pet[] |
低(添加/删除宠物时) | 几只到十只 |
pet_events |
HealthEvent[] |
中(每次健康记录) | 几十到几百条 |
分开存储的优势:
- 写入效率:添加健康事件时只需序列化
events,无需处理pets - 读取效率:加载时可以分开处理,不至于一次加载过多数据
- 数据安全:一个数据源损坏不影响另一个
5.2 存储实现
async loadData(): Promise<void> {
let ctx = getContext(this);
this.pref = await preferences.getPreferences(ctx, 'pet_health_db');
let pv = await this.pref.get(STORAGE_KEY_PETS, '');
if (pv !== '') {
let pd = JSON.parse(pv as string) as Pet[];
if (pd && pd.length > 0) {
this.petList = pd;
if (this.selectedPet === null) {
this.selectedPet = pd[0];
this.selectedPetIndex = 0;
}
}
}
let ev = await this.pref.get(STORAGE_KEY_EVENTS, '');
if (ev !== '') {
let ed = JSON.parse(ev as string) as HealthEvent[];
if (ed && ed.length > 0) this.events = ed;
}
}
async saveData(): Promise<void> {
if (this.pref) {
await this.pref.put(STORAGE_KEY_PETS, JSON.stringify(this.petList));
await this.pref.put(STORAGE_KEY_EVENTS, JSON.stringify(this.events));
await this.pref.flush();
}
}
5.3 级联删除
删除宠物时需要同时删除关联的所有健康事件:
deletePet(petId: number): void {
this.petList = this.petList.filter(p => p.id !== petId);
this.events = this.events.filter(e => e.petId !== petId);
this.showPetDetail = false;
this.saveData();
}
events.filter(e => e.petId !== petId) 一次性过滤掉所有关联事件。
6. 宠物 CRUD 与表单设计
6.1 添加宠物表单
表单包含四个输入:
- 头像选择:8 个 Emoji 头像的 Grid 排布,选中态有边框高亮
- 名字输入:TextInput,必填
- 品种输入:TextInput,选填,默认"未知"
- 出生日期输入:TextInput,格式 YYYY-MM-DD
doAddPet(): void {
if (this.newName.trim() === '') return;
let pet: Pet = {
id: Date.now(),
name: this.newName.trim(),
breed: this.newBreed.trim() || '未知',
birthDate: new Date(this.newBirthDate).getTime(),
avatar: PET_AVATARS[this.newAvatarIndex],
color: PET_COLORS[this.newAvatarIndex],
notes: ''
};
this.petList = [pet].concat(this.petList);
this.showAddPet = false;
this.saveData();
}
6.2 宠物详情弹窗
┌─────────────────────────┐
│ 🐶 │ ← Emoji 头像
│ 小明 │ ← 名字
│ 金毛 · 3岁 │ ← 品种 · 年龄
│ ───────────────── │
│ 💉 3 🔬 2 🤒 1 📋 6 │ ← 健康统计
│ ───────────────── │
│ 🗑️ 删除宠物 │
│ 关闭 │
└─────────────────────────┘
6.3 添加健康记录
doAddEvent(): void {
if (this.eventTitle.trim() === '' || this.selectedPet === null) return;
let evt: HealthEvent = {
id: Date.now(),
petId: this.selectedPet.id,
date: new Date(this.eventDate).getTime(),
type: EVENT_TYPES[this.eventTypeIndex],
title: this.eventTitle.trim(),
detail: this.eventDetail.trim()
};
this.events = [evt].concat(this.events);
this.showAddEvent = false;
this.saveData();
}
6.4 类型选择弹窗
ForEach(EVENT_TYPES, (type: string, idx: number) => {
Row() {
Text(EVENT_ICONS[idx]).fontSize(20)
Text(type).fontSize(15)
.fontColor(this.eventTypeIndex === idx ? this.getEventColor(type) : C.text)
.fontWeight(this.eventTypeIndex === idx ? FontWeight.Bold : FontWeight.Normal)
if (this.eventTypeIndex === idx) {
Text('✓').fontSize(16).fontColor(this.getEventColor(type))
}
}
.onClick(() => { this.eventTypeIndex = idx; this.showTypePicker = false; })
})
选中项显示 ✓ 符号并高亮颜色,用户可清晰看到当前选择。
7. 健康贴士知识库
7.1 六条贴士
this.buildTip('💉', '疫苗接种', '幼犬/幼猫首次接种疫苗一般在6-8周龄...', C.vaccine)
this.buildTip('🔬', '定期体检', '建议每年带宠物做一次全面体检...', C.checkup)
this.buildTip('🍽️', '饮食管理', '定时定量喂食,避免随意换粮...', C.grooming)
this.buildTip('🐾', '运动建议', '小型犬每天30分钟,中大型犬每天1-2小时...', C.med)
this.buildTip('💦', '口腔护理', '建议每周刷牙2-3次,使用宠物专用牙膏...', C.illness)
this.buildTip('🏠', '居家安全', '收好危险物品:巧克力、木糖醇、百合花...', C.other)
7.2 贴士卡片组件
@Builder
buildTip(icon: string, title: string, content: string, color: string) {
Column() {
Row() {
Text(icon).fontSize(24)
Text(title).fontSize(17).fontColor(C.text).fontWeight(FontWeight.Bold)
.margin({ left: 10 })
Blank()
}.width('100%')
Text(content).fontSize(14).fontColor(C.textLight)
.lineHeight(22).margin({ top: 6 }).width('100%')
}
.width('100%').padding(16)
.backgroundColor(C.cardBg).borderRadius(14)
.borderWidth(1).borderColor(color + '33')
.margin({ bottom: 10 })
}
每条贴士的边框颜色与主题对应——疫苗蓝色、体检绿色、饮食橙色等。
8. 紧凑代码风格与错误预防
8.1 问题的提出
在前六款 App 的开发中,@Builder 中的 let 声明是最容易出错的模式。每次新增一个 @Builder,都有可能不小心在内部使用 let。
8.2 紧凑风格的解决方案
从第六款"碎片时间学习"的 851 行到本 App 的 450 行,代码量减少了近一半。这得益于紧凑代码风格:
传统风格(容易引入 let):
@Builder
buildPetCard(pet: Pet) {
let events = this.events.filter(e => e.petId === pet.id);
let lastEvent = events.length > 0 ? events[events.length - 1] : null;
Column() {
// ...
if (lastEvent) {
Text('最近: ' + lastEvent.title)
}
}
}
紧凑风格(消除 let):
@Builder
buildPetList() {
ForEach(this.petList, (pet: Pet) => {
Column() {
// 所有数据直接从方法获取
Text(pet.name)
Text(this.calcAge(pet.birthDate) + '岁')
}
.onClick(() => { this.selectedPet = pet; })
})
}
8.3 紧凑风格的原则
- Builder 内不使用
let:所有数据从参数或方法调用获取 - 链式调用合并到单行:
.fontSize(15).fontColor(C.text).margin({ top: 10 }) - 简单的 Builder 直接返回:
@Builder buildXxx() { Column() { ... } }无需包裹层 - 业务逻辑提取为方法:
calcAge()、getEventColor()、countEv()
8.4 紧凑风格的实际效果对比
以宠物卡片列表为例,对比两种风格的实际效果:
传统风格(可能引入 let):
@Builder
buildPetCard(pet: Pet, index: number) {
let events = this.events.filter(e => e.petId === pet.id);
let lastEvent = events.length > 0 ? events[events.length - 1] : null;
Column() {
Row() {
Text(pet.avatar).fontSize(40).margin({ left: 12 })
Column() {
Text(pet.name).fontSize(18).fontColor(C.text).fontWeight(FontWeight.Bold)
Text(pet.breed).fontSize(12).fontColor(C.textLight)
if (lastEvent) {
Text('最近: ' + lastEvent.title).fontSize(11).fontColor(C.primary)
}
}
}
}
.borderRadius(16).borderWidth(1).borderColor(pet.color + '33')
.margin({ bottom: 12 })
}
紧凑风格(内联在 ForEach 中):
ForEach(this.petList, (pet: Pet, idx: number) => {
Column() {
Row() {
Text(pet.avatar).fontSize(40).margin({ left: 12 })
Column() {
Text(pet.name).fontSize(18).fontColor(C.text).fontWeight(FontWeight.Bold)
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width(160)
Text(pet.breed).fontSize(12).fontColor(C.textLight)
}.margin({ left: 12 }).alignItems(HorizontalAlign.Start).layoutWeight(1)
Text('❯').fontSize(16).fontColor(C.textHint).margin({ right: 12 })
}.width('100%').padding({ top: 14, bottom: 14 })
}.width('100%').backgroundColor(C.cardBg).borderRadius(16).borderWidth(1)
.borderColor(pet.color + '33')
.margin({ bottom: 12 })
.onClick(() => { this.selectedPet = pet; this.showPetDetail = true; })
}, (pet: Pet) => pet.id.toString())
传统风格需要单独的 @Builder 方法(15 行),紧凑风格直接在 ForEach 中完成(14 行)。两者行数相近,但紧凑风格消除了 @Builder 和 let 的使用,减少了错误入口。
8.5 紧凑风格 vs 传统风格对比
| 维度 | 传统风格 | 紧凑风格 |
|---|---|---|
| 可读性 | 容易阅读 | 需要习惯 |
| 错误率 | 较高(容易引入 let) | 低(语法更简单) |
| 代码行数 | 多 | 少 50-60% |
| 修改难度 | 易 | 需注意链式连续性 |
| 适合场景 | 大型团队 | 个人/小团队 |
对于个人开发者或小团队,紧凑风格效率更高;对于大型团队协作,传统风格更易读。
9. 编译错误全记录
9.1 错误概览
本 App 出现 10 个编译错误,分布如下:
| 类型 | 数量 | 占比 | 说明 |
|---|---|---|---|
@Builder 中 let 声明 |
4 | 40% | buildPetDetail、buildTimeline等 |
| 属性不存在 | 3 | 30% | borderBottomWidth、card变量 |
| 对象无类型 | 1 | 10% | COLOR_MAP |
| 级联解析错误 | 2 | 20% | 大量后续错误由单点语法错误引发 |
9.2 关键错误:级联解析错误
这是本 App 遇到的最棘手的错误类型。一个语法错误导致了后面所有方法的解析失败:
现象:buildPetDetail 中的 let pet = this.selectedPet as Pet; 导致该 @Builder 被编译器视为普通函数。而普通函数需要显式返回类型(arkts-no-implicit-return-types),且后续所有方法声明都被视为嵌套在 buildPetDetail 内部(Only UI component syntax can be written here),从而产生超过 200 个错误。
根本原因:ArkTS 编译器的错误恢复能力有限。当遇到一个语法错误时,编译器会继续解析,但解析状态已被破坏,后续的所有声明都会以错误的上下文被解析。
解决方案:重构 buildPetDetail,使用 if (this.selectedPet !== null) 包裹 + this.selectedPet!.property 方式,完全消除 let。
9.3 七款 App 错误统计
| App | 错误数 | Builder 错误占比 | 修复策略 |
|---|---|---|---|
| 白噪音 | 16 | 50% | 学习基础语法 |
| 时间胶囊 | 17 | 53% | 掌握 Builder 约束 |
| 冰箱剩菜 | 22 | 68% | 提取计算方法 |
| 尴尬粉碎机 | 1 | 0% | 仅颜色接口错误 |
| 防骗训练 | 12 | 50% | 大段 Builder 重构 |
| 碎片学习 | 12 | 58% | 方法调用替代 let |
| 宠物日记 | 10 | 40% | 紧凑风格彻底消除 let |
关键洞察:Builder 错误始终是最高比例的错误类型。紧凑风格通过缩短 Builder 方法的行数、减少逻辑复杂度,有效降低了错误率。
10. 七款 App 系列总结
10.1 数据模型演变
| App | 持久化 | 模型结构 |
|---|---|---|
| 白噪音 | 无 | 单数组(预设) |
| 时间胶囊 | 单键值 | 单数组(用户生成) |
| 冰箱剩菜 | 3 键值 | 数组 + 结构体 |
| 尴尬粉碎机 | 单键值 | 静态 + 用户数组 |
| 防骗训练 | 单键值 | 静态 + 进度对象 |
| 碎片学习 | 单键值 | 静态 + 记录对象 |
| 宠物日记 | 双键值 | 数组 + 数组 |
10.2 架构复杂度
白噪音 ─┬→ 时间胶囊 ─┬→ 冰箱剩菜 ─┬→ 尴尬粉碎机 ─┬→ 防骗训练 ─┬→ 碎片学习 ─┬→ 宠物日记
单页 单页+弹窗 三Tab+Grid 三Tab+列表 三Tab+情景 三Tab+卡片 三Tab+时间线
767行 955行 1320行 953行 1038行 851行 450行
10.3 编译错误趋势
▲ 22 (冰箱剩菜)
│
│ 17 (时间胶囊)
│ 16 (白噪音)
│
│ 12 (防骗训练) 12 (碎片学习)
│ 10 (宠物日记)
│
│ 1 (尴尬粉碎机)
└──────────────────────────────────────────▶
App1 App2 App3 App4 App5 App6 App7
曲线呈现"升→降→平"的趋势,从探索期的攀升到成熟期的平稳。
10.4 核心模式清单
模式 1:Tab 架构
@State activeTab: number = 0;
buildTabContent() {
if (activeTab === 0) buildTab0()
else if (activeTab === 1) buildTab1()
else buildTab2()
}
模式 2:Stack 三层
Stack() {
buildBackground()
Column() { buildHeader(); buildTabContent(); buildTabBar() }
if (dialog) { buildDialog() }
}
模式 3:弹窗
@Builder buildDialog() {
if (this.show) {
Column() {
Column().onClick(() => { this.show = false; }) // 蒙层
Column() { /* 内容 */ } // 浮层
}
}
}
模式 4:数据持久化
aboutToAppear() → loadData()
每次操作后 → saveData()
aboutToDisappear() → saveData()
模式 5:数组更新
this.list = [newItem].concat(this.list); // 插入
this.list = this.list.concat([]); // 触发渲染
this.list = this.list.filter(predicate); // 删除
模式 6:@Builder 紧凑风格
@Builder buildXxx() {
// 不用 let,直接从参数或方法获取数据
Text(this.getData(param))
}
10.5 七篇博客总览
| # | 博客标题 | App | 核心技术 | 字数 |
|---|---|---|---|---|
| 1 | 沉浸式白噪音 | 🎵 白噪音 | 多媒体 + 动画 | ~11,000 |
| 2 | 时间胶囊 | ⏳ 时间胶囊 | Preferences + Builder | ~11,000 |
| 3 | 冰箱剩菜大作战 | 🧊 冰箱剩菜 | Tab + 游戏化 | ~10,800 |
| 4 | 尴尬粉碎机 | 😅 尴尬粉碎 | 静态数据 + 模式 | ~9,600 |
| 5 | 老年人防骗训练 | 🛡️ 防骗训练 | 适老化 + 情景 | ~8,800 |
| 6 | 碎片时间学习 | 💡 碎片学习 | 知识卡片 + 激励 | ~8,700 |
| 7 | 宠物健康日记 | 🐶 宠物日记 | 双键持久化 + 紧凑风格 | ~11,000 |
11. HarmonyOS 开发终章感言
11.1 从零到一的旅程
从第一篇博客的"沉浸式白噪音"到本篇"宠物健康日记",我们走过了:
- 7 款完整 App
- 超过 70,000 字的技术博客
- 约 6,300 行 ArkTS 代码
- 约 90 个编译错误的修复
这些数字背后,是从一个完全陌生的开发框架到熟练掌握其核心模式的完整学习曲线。
11.2 对 ArkUI 的评价
经过七个项目的实践,我对 ArkUI 的评价如下:
优势:
- 声明式 DSL 的开发效率高,组件树结构清晰
- 编译期优化带来原生性能
- @Builder + @State 的组合足够表达大多数 UI 交互
- Preferences API 简单易用
不足:
- @Builder 的语法约束过于严格(不能用 let、return 等)
- 错误恢复能力有限(一个语法错误可能级联出几百个错误)
- 某些 API(如 flexWrap、borderBottomWidth)在文档中存在但实际不支持
- 展开运算符(spread operator)不支持,需要用 concat 替代
11.3 给后来者的建议
- 从 Tab 架构开始:Tab 架构是最通用的模式,几乎所有内容型 App 都适用
- Builder 不放逻辑:这是 ArkUI 最重要的一条规则,违反它会导致最多的编译错误
- 先设计数据模型:写 UI 前先定义好 interface,能避免一半的返工
- 紧凑风格降低错误:Builder 越短,出错概率越低
- 每次 build 只改一个错误:当出现级联错误时,修复最前面的那个,后面的可能自动消失
11.4 结语
七款 App,七篇博客——这是一次完整的 HarmonyOS Next 开发学习之旅。
从第一行 ArkTS 代码到最后一篇技术博客,我们验证了一个事实:HarmonyOS 应用开发的学习曲线虽然陡峭,但一旦掌握核心模式,开发效率会大幅提升。
如果你是从第一篇一路读到这里的读者,感谢你的陪伴。现在,打开 DevEco Studio,开始你自己的第一个 ArkUI 项目吧。
附录 A:颜色主题速查表
| 名称 | 色值 | 用途 |
|---|---|---|
| primary | #FF7043 |
主色、Tab 选中、按钮 |
| vaccine | #42A5F5 |
🟦 疫苗记录 |
| checkup | #66BB6A |
🟩 体检记录 |
| med | #AB47BC |
🟪 用药记录 |
| illness | #EF5350 |
🟥 生病记录 |
| grooming | #FFA726 |
🟧 美容记录 |
| other | #78909C |
⬜ 其他记录 |
附录 B:七款 App 一键速查
| # | App | 行数 | 错误数 | Builder 数 | 持久化 | Tab 数 |
|---|---|---|---|---|---|---|
| 1 | 白噪音 | 767 | 16 | 8 | 无 | 1 |
| 2 | 时间胶囊 | 955 | 17 | 12 | 单键 | 1 |
| 3 | 冰箱剩菜 | 1320 | 22 | 17 | 3键 | 3 |
| 4 | 尴尬粉碎机 | 953 | 1 | 15 | 单键 | 3 |
| 5 | 防骗训练 | 1038 | 12 | 14 | 单键 | 3 |
| 6 | 碎片学习 | 851 | 12 | 14 | 单键 | 3 |
| 7 | 宠物日记 | 450 | 10 | 12 | 双键 | 3 |
附录 C:ArkUI 开发模式卡片
| 场景 | 模式 | 参考 App |
|---|---|---|
| 首页列表 + 详情弹窗 | List + ForEach + Dialog | 时间胶囊、宠物日记 |
| 三 Tab 切换 | activeTab + 条件渲染 | 冰箱剩菜到宠物日记 |
| 数据持久化 | Preferences + JSON | App2 到 App7 |
| 滑动删除 | swipeAction | 时间胶囊、冰箱剩菜 |
| 分类/单位选择 | Grid 弹窗 | 冰箱剩菜 |
| 学习激励 | 连续天数 + 鼓励语 | 碎片学习 |
| 适老化 | 大字体 + 高对比度 | 防骗训练 |
| 紧凑 Builder | 无 let + 链式单行 | 宠物日记 |
更多推荐



所有评论(0)