鸿蒙 Next 家庭记忆时光机 App 开发实战:记忆时间线 + 情感化设计 + 预览器兼容性



鸿蒙 Next 家庭记忆时光机 App 开发实战:记忆时间线 + 情感化设计 + 预览器兼容性
作者:duluo
SDK 版本:HarmonyOS API 24 (Next)
开发工具:DevEco Studio
语言框架:ArkTS + ArkUI
字数:约 9600 字
目录
1. 引言
1.1 为什么需要"记忆时光机"
你有没有这样的时刻——
翻到一张老照片,看到照片里的场景,但想不起来那是哪一年、在哪里、当时发生了什么。
手机里有几千张照片,但真正被翻看的,可能不到 1%。
"家庭记忆时光机"要解决的问题很简单:用文字留住记忆。不是替代照片,而是在照片之外,用几段话记录下那些"当时觉得普通、回头看却很珍贵"的瞬间。
1.2 与同类 App 的差异
市面上已经有大量的日记 App、笔记 App、相册 App。但"家庭记忆时光机"的定位不同:
| 日记 App | 相册 App | 本 App | |
|---|---|---|---|
| 核心内容 | 每日心情 | 照片 | 精选记忆 |
| 记录频率 | 每天 | 随时 | 值得记的才记 |
| 呈现方式 | 按日期列表 | 按时间网格 | 卡片时间线 |
| 情感设计 | 中性 | 中性 | 温暖复古 |
| 目标用户 | 个人 | 个人 | 家庭 |
本 App 的核心理念是:少即是多。不要求每天记录,只记录那些"值得记住"的瞬间。10 条精心挑选的回忆,比 1000 张无人问津的照片更有温度。
1.3 家庭记忆的特殊性
家庭记忆和个人记忆有一个本质区别:所有权。
个人日记是"我的记忆"——写给自己看的,不需要考虑读者的感受。但家庭记忆是"我们的记忆"——写的时候会想着"以后孩子看到这条会怎么想"、“老伴看到这条会不会笑”。
这种"为他人记录"的心态影响了 App 的设计:
个人日记 → 随性、情绪化、可能负能量
家庭记忆 → 精选、温暖、正能量的片段
所以本 App 在设计时有意去掉了"今日心情"、"情绪标签"等功能——不是它们不好,而是它们更适合个人日记场景。家庭记忆场景需要的是:干净的记录、温暖的回看、简单的分享。
1.4 视觉风格的怀旧感
本 App 的视觉设计刻意营造了一种"旧时光"的氛围:
- 主色
#B8860B(复古金):这是古代书画中常用的金色调,带有时间的厚重感 - 背景
#F5EDE0(米黄):模仿老照片泛黄的色调 - 深棕文字
#3D2B1F:像旧书页上的油墨
颜色本身就有情感属性。冷色调(蓝、灰)传达"高效、专业",暖色调(米黄、金棕)传达"温暖、怀旧"。家庭记忆 App 选择了暖色调——每次打开都像翻开一本旧相册。
1.5 本 App 的技术特色
- 卡片时间线:按时间倒序排列的卡片列表,每条记忆以 emoji + 标题 + 摘要展示
- 极简表单:标题输入 + 年份选择,快速记录
- 详情弹窗:点击卡片查看完整内容
- 预览器兼容:从设计之初就排除了预览器不支持的 API
- 渐进式复杂度:从 479 行精简到 188 行,移除所有不必要的抽象
1.6 二十八款 App 全景
App 数量: 28
代码总行数: ~16,600 行
编译错误数: ~266 个
博客总字数: ~282,000 字
技术博客数: 28 篇
2. 产品概念与记忆模型
2.1 功能需求
用户故事 1:我想把一家人的快乐瞬间记下来
用户故事 2:我想按时间顺序翻看以前的记忆
用户故事 3:我想给每段记忆配一个表情符号
用户故事 4:我想知道一共记录了多少段记忆
功能清单:
├── F1: 记忆时间线(按时间倒序)
├── F2: 添加记忆(标题 + 年份)
├── F3: 记忆详情弹窗
├── F4: 统计概览(总条数)
├── F5: 预置示例记忆
└── F6: 三 Tab 切换
2.2 数据模型
interface Memory {
id: number; // 唯一标识
date: number; // 时间戳
year: number; // 年份
emoji: string; // 情绪/主题图标
title: string; // 记忆标题
content: string; // 记忆正文
}
这是本系列中最简洁的数据模型之一。6 个字段,涵盖了记忆的核心要素:时间(date + year)、情感(emoji)、标题、内容。
为什么不用照片:照片存储需要文件 I/O 权限,在预览器中不可用。文字版记忆可以在预览器中完整运行。
2.3 预置示例
const DEMO: Memory[] = [
{ id: 1, date: 1703462400000, year: 2024, emoji: '🎄', title: '圣诞节', content: '一起装饰了圣诞树,吃了火锅。' },
{ id: 2, date: 1727856000000, year: 2024, emoji: '🏖️', title: '海边旅行', content: '第一次看到大海,很开心。' },
{ id: 3, date: 1696032000000, year: 2023, emoji: '🎂', title: '生日', content: '全家人一起过的生日。' }
];
为什么使用时间戳常量而不是 new Date():在 ArkTS 中,顶层常量的初始化发生在模块加载时。如果使用 new Date(2024, 11, 25),Previewer 需要解析 Date 构造函数,这在某些预览器版本中可能失败。使用毫秒时间戳常量彻底避免了这个问题。
3. 三 Tab 架构设计
3.1 Tab 配置
build() {
Stack() {
Column().backgroundColor(C.bg)
Column() {
this.buildHeader()
if (this.activeTab === 0) this.buildTimeline()
else if (this.activeTab === 1) this.buildAdd()
else this.buildStats()
this.buildTabBar()
}
if (this.showMemory) this.buildDetail()
if (this.showAdd) this.buildAddForm()
}
}
| Tab | 图标 | 功能 |
|---|---|---|
| 0 | 📅 | 时间线 — 浏览所有记忆 |
| 1 | ✏️ | 添加 — 记录新记忆 |
| 2 | 📊 | 统计 — 查看总条数 |
弹窗处理:buildDetail() 和 buildAddForm() 使用了最小化的 if 包裹,没有任何额外的生命周期逻辑。弹窗打开和关闭只涉及 @State 变量的设置,不涉及任何异步操作。
3.2 顶部栏
@Builder
buildHeader() {
Row() {
Text('🕰️ 记忆时光机').fontSize(20).fontColor(C.text).fontWeight(FontWeight.Bold)
Blank()
}.width('100%').padding({ left: 20, right: 20, top: 48, bottom: 8 })
}
本 App 的顶部栏是系列中最简单的——只有一个标题,没有副标题、没有计数、没有操作按钮。所有操作都通过 Tab 和弹窗完成。
3.3 三 Tab 数据流
添加记忆 → memories 数组头部插入 → @State 自动更新
↓
时间线 Tab ← 读取 memories(按时间倒序展示)
统计 Tab ← 读取 memories.length(总条数)
详情弹窗 ← 通过索引访问 memories[idx]
所有数据流都是单向的——从添加操作到列表更新,再到统计展示。不需要任何中间状态管理。
4. 时间线实现
4.1 卡片列表
@Builder
buildTimeline() {
Column() {
if (this.memories.length === 0) {
Text('暂无记忆').fontSize(16).fontColor(C.textMuted)
} else {
Scroll() {
Column() {
ForEach(this.memories, (mem: Memory) => {
Column() {
Row() {
Text(mem.emoji).fontSize(28)
Column() {
Text(mem.title).fontSize(16).fontColor(C.text).fontWeight(FontWeight.Bold)
Text(mem.year + '年').fontSize(12).fontColor(C.textMuted).margin({ top: 2 })
Text(mem.content).fontSize(13).fontColor(C.textLight).maxLines(2)
}.margin({ left: 10 }).layoutWeight(1)
}.width('100%').padding(14)
}.width('100%').backgroundColor(C.bgCard).borderRadius(16).margin({ bottom: 6 })
.onClick(() => {
this.selectedMemory = this.memories.indexOf(mem);
this.showMemory = true;
})
}, (mem: Memory) => mem.id.toString())
}.padding({ left: 16, right: 16 })
}.layoutWeight(1)
}
}.layoutWeight(1)
}
卡片布局:左侧 emoji(28sp)、右侧三行信息(标题 + 年份 + 预览)。maxLines(2) 控制预览行数,超出省略。整个卡片 16px 圆角 + 6px 间距,形成呼吸感。
ForEach 的 key 函数:使用 mem.id.toString() 作为唯一标识。id 是 Date.now() 生成的数字,保证在同一次运行中唯一。
4.2 点击查看详情
.onClick(() => {
this.selectedMemory = this.memories.indexOf(mem);
this.showMemory = true;
})
indexOf(mem) 获取该记忆在数组中的索引,然后通过索引去详情弹窗中读取完整内容。这种"引用转索引"的模式避免了在 @Builder 中存储对象引用。
5. 添加记忆与表单设计
5.1 添加入口
@Builder
buildAdd() {
Column() {
Blank().layoutWeight(1)
Text('✏️').fontSize(48)
Text('记录新记忆').fontSize(18).fontColor(C.primary).fontWeight(FontWeight.Bold).margin({ top: 8 })
Text('写下来,让温暖不被遗忘').fontSize(14).fontColor(C.textMuted).margin({ top: 4 })
Blank().layoutWeight(1)
}.width('100%').alignItems(HorizontalAlign.Center)
.onClick(() => {
this.newEmoji = '💖';
this.newTitle = '';
this.newContent = '';
this.newYear = 2025;
this.showAdd = true;
})
}
添加 Tab 不像传统表单那样直接展示输入框,而是展示一个"引导页面"——一个大大的 ✏️ 图标,加一句温暖的话,点击后才打开弹窗表单。
这种设计有两个好处:
- 降低心理门槛:看到空白的表单会让人犹豫"要写什么",看到引导页只会让人想"点一下看看"
- Tab 切换不变:填写表单时如果切换到其他 Tab,弹窗关闭但数据不丢失
5.2 添加弹窗
@Builder
buildAddForm() {
Column() {
// 遮罩层
Column().onClick(() => { this.showAdd = false; })
// 表单卡片
Column() {
Text('记录记忆').fontSize(18).fontColor(C.text)
TextInput({ placeholder: '标题', text: this.newTitle })
.fontSize(16).height(44).backgroundColor(C.bg).borderRadius(12)
Text('保存').fontSize(16).fontColor(Color.White)
.backgroundColor(C.primary).borderRadius(20)
.onClick(() => {
if (this.newTitle.trim().length > 0) {
const mem: Memory = {
id: Date.now(), date: Date.now(),
year: this.newYear, emoji: this.newEmoji,
title: this.newTitle.trim(), content: this.newContent.trim()
};
this.memories = [mem, ...this.memories];
this.showAdd = false;
}
})
}
}
}
表单最少字段原则:只有标题是必填项。年份默认当前年,内容可选。最简记录路径:打字 → 点保存 → 完成。整个过程不超过 5 秒。
保存逻辑:
- 创建 Memory 对象:
id = Date.now()(唯一标识)、date = Date.now()(当前时间戳) - 头部插入:
[mem, ...this.memories](最新的在最前面) - 关闭弹窗:
this.showAdd = false - @State 自动触发时间线 Tab 更新
6. 记忆详情弹窗
6.1 弹窗结构
@Builder
buildDetail() {
if (this.selectedMemory >= 0 && this.selectedMemory < this.memories.length) {
Column() {
// 遮罩层
Column().backgroundColor('rgba(0,0,0,0.3)')
.onClick(() => { this.showMemory = false; })
// 详情卡片
Column() {
Text(this.memories[this.selectedMemory].emoji).fontSize(56)
Text(this.memories[this.selectedMemory].title).fontSize(20)
Text(this.memories[this.selectedMemory].content).fontSize(15)
Text('关闭').onClick(() => { this.showMemory = false; })
}.width('85%').backgroundColor(C.bgCard).borderRadius(24)
}
}
}
弹窗的视觉层次从上到下:大 emoji → 标题 → 正文 → 关闭按钮。所有文字通过 this.memories[this.selectedMemory] 从数组索引读取。
为什么不用 const mem = this.memories[this.selectedMemory]:因为 ArkTS 的 @Builder 中不允许 const 声明。所有变量必须通过内联表达式访问。
6.2 安全性检查
if (this.selectedMemory >= 0 && this.selectedMemory < this.memories.length) {
弹窗渲染前检查索引是否越界。这个检查在正常操作中是多余的(selectedMemory 总是通过 indexOf 设置),但防御性编程的习惯可以避免极端情况下的白屏。
7. 统计概览
7.1 统计页
@Builder
buildStats() {
Column() {
Blank().layoutWeight(1)
Text('共 ' + this.memories.length + ' 条记忆').fontSize(20).fontColor(C.text)
Blank().layoutWeight(1)
}
}
统计页是系列中最简单的——只有一行文字,显示总记忆条数。没有图表、没有柱状图、没有年度分布。
有意识的设计决策:在 App 的初始版本中,只展示最核心的指标。后续版本可以根据需要添加:按年份分布、月度热力图、最长连续记录等。
8. 预览器兼容性修复
8.1 白屏诊断
本 App 在开发过程中经历了一次典型的"白屏"问题。构建成功(0 错误),但预览器显示空白。
诊断过程:
Round 1: 构建成功 → 预览白屏
→ 怀疑 Set 不支持 → 移除所有 Set
→ 仍白屏
Round 2: 构建成功 → 预览白屏
→ 怀疑 for...of 不支持 → 移除所有 for...of
→ 仍白屏
Round 3: 构建成功 → 预览白屏
→ 怀疑 Date() 静态初始化 → 替换为时间戳常量
→ 仍白屏
Round 4: 构建成功 → 预览白屏
→ 简化代码到最小可运行版本(188 行)
→ ✅ 预览成功
Round 5: 逐步添加功能
→ 确认每个功能的预览器兼容性
根本原因是多个因素的叠加:当代码同时使用了 Set、for…of、嵌套 ForEach、Date 静态初始化时,预览器无法正确处理。但单独移除任何一个,问题仍然存在。只有同时简化到最基础的结构,问题才消失。
8.2 预览器兼容清单
经过本 App 的排查,以下是 ArkTS Previewer 中已知的不完全支持的功能:
| 功能 | 支持情况 | 替代方案 |
|---|---|---|
Set / Map |
⚠️ 部分支持 | 使用数组 + indexOf |
for...of 迭代 |
⚠️ 可能有问题 | 使用 for 循环 + 索引 |
顶层 new Date() |
⚠️ 可能失败 | 使用时间戳常量 |
嵌套 ForEach |
⚠️ 可能崩溃 | 减少嵌套层级 |
@Builder 中 const |
❌ 不支持 | 内联表达式 |
Grid 组件 |
⚠️ 部分版本有问题 | 使用 Column + Flex |
Set.size |
⚠️ 可能为 undefined | 使用 Array.length |
8.3 最简可行原则
最终版本的代码只有 188 行,是所有复杂功能移除后的最小子集。
移除的功能:
- 年份分组(嵌套 ForEach)
- 年度统计柱状图(Grid)
- 预置 12 条示例数据(减少为 3 条)
- getYearGroups / getYearSummary 等辅助方法
保留的功能:
- 三 Tab 切换
- 时间线卡片列表
- 添加记忆弹窗
- 记忆详情弹窗
- 统计概览
这个"最简可行版本"原则不仅解决了预览器兼容性问题,也让代码更容易理解和维护。
9. 编译错误全记录
9.1 错误概览
本 App 共出现 8 个编译错误。
| # | 错误代码 | 位置 | 原因 | 修复 |
|---|---|---|---|---|
| 1-2 | 10605074 | L335, 341 | Map 解构 const [y, c] of map |
改为数组 + indexOf |
| 3 | 10505001 | L193 | getYearGroups 返回类型不匹配 |
改为返回 number[] |
| 4 | 10905209 | L146 | ForEach 中 const year = ... |
改为内联 |
| 5-7 | 10905209 | L279-283 | ForEach 中 const year/count/maxCount |
改为内联 |
| 8 | 10905209 | L361 | const mem = ... |
改为内联 |
9.2 错误分类
10605074(解构): 2 个 → 25%
10505001(类型): 1 个 → 12.5%
10905209(Builder):5 个 → 62.5%
10905209 仍然是最常见的错误类型,占总数的 62.5%。从 App 1 到 App 28,这个错误从未缺席。
10605074(解构)在本 App 中出现了 2 次,都是因为使用了 Map 的 for (const [key, value] of map) 语法。替换为数组 + indexOf 后修复。
9.3 28 款 App 的错误趋势
App 1: 16 ← 初学
App 10: 11 ← 模式形成
App 20: 2 ← 高效期
App 24: 48 ← 新领域(AI 对话)
App 25: 3 ← 回归基线
App 26: 8 ← 全部同一类型
App 27: 5 ← 全部低级错误
App 28: 8 ← 包含 2 种类型
App 28 的错误分布呈现了典型的"稳定期模式":大部分是 10905209(5 个),小部分是新的语法错误(2 个 10605074 + 1 个 10505001)。
9.4 经验总结
经过 28 款 App 的实践,以下规则已经变成了"肌肉记忆":
已经不会犯的错误:
- 颜色常量忘加 interface ✅
- ForEach key 函数作用域 ✅
- 索引签名 ✅
- 数字键名 ✅
偶尔还会犯的错误:
- @Builder 中写逻辑 ⚠️(第 28 次)
- 解构赋值 ⚠️(第 2 次)
- 方法返回类型不匹配 ⚠️(重构时的遗留)
10. 从复杂到极简的演进
10.1 版本对比
| 维度 | 初始版(479 行) | 最终版(188 行) | 变化 |
|---|---|---|---|
| 代码行数 | 479 | 188 | -61% |
| 文件体积 | 16.5 KB | 6.2 KB | -62% |
| @Builder 数量 | 7 个 | 6 个 | -1 |
| 业务方法 | 11 个 | 2 个 | -9 |
| 预览兼容性 | ❌ 白屏 | ✅ 正常 | 修复 |
| 编译错误 | 8 个 | 0 个 | 修复 |
代码精简的来源:
功能移除:
年份分组展示 → -60 行
年度柱状图 → -40 行
12 条预置数据 → -30 行
getYearGroups() → -20 行
getYearSummary() → -25 行
getMaxYearCount() → -10 行
语法精简:
const 移除(内联化)→ -15 行
空白行压缩 → -20 行
注释精简 → -15 行
总计减少:约 235 行
10.2 功能的取舍
为什么移除"年份分组"和"柱状图"?
年份分组:从用户角度来看,2024 年 → 回忆 1 → 回忆 2 → 2023 年 → 回忆 3 这种分组确实好看。但从技术角度来看,它需要一个嵌套 ForEach(ForEach 中套 ForEach),这是 ArkTS 预览器中最容易出问题的模式之一。
而且从产品角度来看,年份分组在数据量少的时候意义不大——只有 3 条记忆,分 2 个年份,每组最多 2 条,分组的视觉效果还不如直接按时间排列。只有数据量超过 20 条时,分组才开始展现价值。
年度柱状图:需要 Grid 组件 + 宽度百分比计算 + 多方法调用。对于"总条数"这个信息来说,柱状图的视觉价值大于信息价值。用户看到柱状图的第一反应是"哦,今年记了 3 条",而不是"这个杠的长度刚好是 30%"——他们只需要数字。
编辑和删除功能:初始版本包含了编辑和删除,但最终版本去掉了。原因有二:第一,编辑功能需要在弹窗中加载现有数据并允许修改,这增加了至少 30 行代码和一个新的 @State 变量;第二,对于一个"记录美好回忆"的 App,删除功能的存在暗示了"有些记忆不值得保留",这与产品理念相悖。
取舍原则:如果某个功能移除后,App 的核心价值(记录和浏览家庭记忆)不受影响,那就是可以移除的。
10.3 极简主义的十二字方针
本 App 的极简设计可以总结为十二个字:
看得见:打开 App 立刻看到时间线,不需要任何操作
记得快:点 Tab → 点中间 → 打字 → 保存,4 步完成记录
读得懂:每条卡片显示 emoji + 标题 + 年份 + 预览,不需要点进去就知道是什么
这十二个字对应了 App 的三个核心场景:浏览、记录、回顾。
看得见:时间线 Tab 是默认首页。打开 App 时,如果已经有记忆,直接展示卡片列表;如果没有,显示空状态引导文字。用户不需要学习任何操作就能看到内容。
记得快:从打开 App 到完成一条记忆的记录,最多 4 次点击。不需要选择日期(默认当前年)、不需要选择分类、不需要添加标签。标题写完后直接点保存,整个过程不超过 10 秒。
读得懂:每条卡片展示了四个信息维度——emoji(情绪)、标题(主题)、年份(时间)、内容摘要(预览)。用户在浏览时间线时,不需要点进详情就能了解每条记忆的大致内容。
10.4 极简主义的设计哲学
188 行代码的 App,能做的事:
- ✅ 浏览记忆(时间线)
- ✅ 添加记忆(标题最少)
- ✅ 查看详情(弹窗)
- ✅ 统计总条数
- ✅ 三 Tab 切换
不能做的事:
- ❌ 按年份分组浏览
- ❌ 柱状图统计
- ❌ 编辑/删除记忆
- ❌ 图片/视频附件
- ❌ 数据持久化
这些"不能做的事"中,有些是后续版本可以加的(编辑/删除/持久化),有些是设计上就不打算做的(图片/视频)。
关键认知:388 行的版本和 188 行的版本,核心价值相同。用户不会因为"少了一个柱状图"而觉得 App 不好用——他们只会因为"打开就能记、记完就能看"而觉得 App 顺手。
11. 第二十八款 App 全景回顾
11.1 数据总览
| 指标 | 数值 |
|---|---|
| 代码行数 | 188 行 |
| 编译错误数 | 8 个(修复后 0) |
| @State 变量 | 9 个 |
| @Builder 方法 | 6 个 |
| 弹窗数 | 2 个 |
| 外部依赖 | 0 个 |
| 预置数据 | 3 条 |
11.2 二十八款 App 行数趋势
App 1: 767 ← 白噪音
App 8: 390 ← 情绪垃圾桶
App 16: 614 ← 梦境解析
App 24: 907 ← AI 树洞(峰值)
App 25: 488 ← 关怀平替
App 26: 508 ← 断段打卡
App 27: 340 ← 长辈说明书
App 28: 188 ← 记忆时光机(新低)
188 行 — 28 款 App 中代码量最少。打破了之前 App 27(长辈说明书,340 行)的记录。
这个下降趋势反映了两个事实:
- ArkUI 的开发效率在提升——同样功能用更少的代码实现
- 开发者对"什么功能是必要的"判断越来越清晰
11.3 二十八款 App 的核心发现
经过 28 款 App 的实践,以下是关于 ArkTS 开发的核心发现:
发现一:@Builder 中不能写逻辑 — 100% 遇到率
28 款 App,每一款都至少遇到一次 10905209 错误。这是 ArkTS 开发中唯一一个"一定会遇到"的错误。
发现二:预览器兼容性 — 最大坑
构建成功 ≠ 预览成功。预览器有自己的运行时限制,不支持的 API 会导致白屏,且错误信息不可见。
发现三:越简单的代码越稳定
188 行的 App 比 907 行的 App 更容易调试、更容易预览、更容易维护。
发现四:代码行数 ≠ 用户体验
188 行的 App 可以记录家庭记忆、浏览时间线、查看详情。907 行的 App 多了 4 个 AI 角色和 164 条回应模板——但核心交互(点击→看到内容)没有区别。
12. 结语
12.1 记忆的价值
「家庭记忆时光机」这款 App,技术上没有任何突破:没有 AI、没有动画、没有复杂的数据结构。只有 188 行代码、3 条预置记忆、2 个弹窗。
但它想传达的东西很简单:那些看起来普通的日常,回过头去看都是珍贵的记忆。
一起装饰圣诞树的下午、第一次看到大海的惊喜、全家人围在一起吃蛋糕的晚上——这些时刻在被记录的时候可能觉得"没什么特别的",但三年五年后翻出来看,心里会涌起一股暖意。
12.2 28 款 App 的旅程
App 1: 听说 ArkUI 是声明式的,试试
App 10: 原来 @Builder 不能写 let 啊
App 20: 28 篇博客,28 万字的记录
App 24: 48 个错误 — 那是探索的代价
App 28: 188 行 — 原来简单才是终点
从 907 行(AI 树洞)到 188 行(记忆时光机),这个旅程不仅是代码量的减少,更是对"什么是必要的"这个问题的持续追问。
AI 树洞的 907 行代码中,有 400 行是 AI 回应模板——这个功能对于 App 的核心价值(被倾听)是必要的。
记忆时光机的 188 行代码中,每一行都直接服务于核心价值(记录和浏览记忆)——没有"也许以后会用"的功能,没有"看起来更高级"的抽象。
12.3 给开发者的建议
- 从最小版本开始:先写 200 行能跑起来的版本,再考虑加功能。不要在一开始就想"万一用户需要这个怎么办"——你还没有用户
- 预览器不支持的 API:Set、Map、for…of、顶层 Date()——用数组和 for 循环替代。预览器是开发者的第一道防线,确保预览器能跑,模拟器和真机通常也不会有问题
- 一个功能一个测试:加一个新功能后立刻预览一下,不要等到加了 5 个功能再预览。如果全崩了,你根本不知道是哪个新功能引起的
- 188 行可以是完整的 App:不要觉得代码少就不够好。188 行可以记录家庭记忆、浏览时间线、查看详情——对用户来说,功能是完整的
- 记录比完美更重要:10 条朴素的记忆,比一个完美的空 App 更有温度。不要在"用什么颜色"、"用什么字体"上纠结太久,先让用户能记下第一条记忆
- 删除功能也是一种设计:加功能很容易,删功能需要勇气。但每次删掉一个不必要的功能,你的 App 就会变得更清晰、更稳定、更容易维护
12.4 二十八款 App 之后的思考
写到这里,这个系列已经覆盖了 28 款 App、28 篇博客。从第一款的"试试 ArkUI"到现在的"188 行记录家庭记忆",最深的感受是:
开发者的成长曲线不是加法,是减法。
刚开始的时候,觉得功能越多越好——Tab 越多越好、弹窗越多越好、动画越多越好。写了二十多款 App 之后,开始觉得"这个功能可以去掉"、“这个页面可以合并”、“这行代码可以删除”。
删除比添加更需要判断力。添加一个功能只需要知道"怎么实现",删除一个功能需要判断"真的需要吗"。这个判断力只能通过实践积累——没有捷径。
如果这个系列能给你一个启发,那就是:先写出来,再删到不能删。 Python 之禅里说"Simple is better than complex",在 ArkTS 开发中尤其如此。
12.5 感谢
28 款 App、28 篇博客、约 282,000 字。
从白噪音到记忆时光机,从 907 行到 188 行——这个系列见证了 ArkTS 的成熟,也见证了开发者的成长。
28 不是终点。如果你正在读这篇文章,也正在写你自己的 App 的路上——不管那是第 1 款还是第 100 款——记住:代码可以不断精简,但记录本身就有价值。
现在,打开 DevEco Studio,写下你的第 1 条记忆吧。
附录 A:核心代码速查
数据模型
interface Memory {
id: number; date: number; year: number;
emoji: string; title: string; content: string;
}
添加记忆
const mem: Memory = {
id: Date.now(), date: Date.now(),
year: this.newYear, emoji: this.newEmoji,
title: this.newTitle.trim(), content: this.newContent.trim()
};
this.memories = [mem, ...this.memories];
时间线卡片
ForEach(this.memories, (mem: Memory) => {
Column() {
Text(mem.emoji).fontSize(28)
Text(mem.title).fontSize(16).fontWeight(FontWeight.Bold)
Text(mem.year + '年').fontSize(12)
Text(mem.content).fontSize(13).maxLines(2)
}.onClick(() => { this.showMemory = true; })
}, (mem: Memory) => mem.id.toString())
附录 B:色板
| 变量 | 值 | 用途 |
|---|---|---|
C.bg |
#F5EDE0 |
主背景(米黄) |
C.bgCard |
#FFFAF0 |
卡片背景 |
C.primary |
#B8860B |
主色(复古金) |
C.primaryDim |
rgba(184,134,11,0.1) |
光晕效果 |
C.text |
#3D2B1F |
主文字(深棕) |
附录 C:预览器兼容检查清单
初始化:
□ 不使用 Set/Map(用数组替代)
□ 顶层不使用 new Date()(用时间戳常量)
□ 顶层不使用复杂计算
@Builder:
□ 不使用 const/let 声明变量
□ 不使用 for 循环
□ 不使用 if-else 中的逻辑计算
ForEach:
□ 始终提供 key 函数(第三个参数)
□ 避免嵌套 ForEach
组件:
□ 优先使用 Scroll + Column,而不是 Grid
□ 弹窗使用 if 包裹 + Stack 层级
更多推荐



所有评论(0)