ArkTS 口语陪练:基于 HarmonyOS 的 21 天语言训练营开发全流程详解
ArkTS 口语陪练:基于 HarmonyOS 的 21 天语言训练营开发全流程详解
本文将以一个完整的 HarmonyOS 应用 ——「21 天语言训练营」为例,从项目背景、技术选型、目录结构、核心功能实现、UI 设计、关键技术细节到性能优化,进行一次系统化的深度拆解。读完之后,你不仅能复刻本项目,还能掌握 ArkTS 在 UI 构建、状态管理、路由导航、列表与网格控件上的全部最佳实践。文章末尾还附带了完整的扩展方向与未来演进路线图,帮助你把项目从"能跑"打磨到"能用"再到"能商业化"。
运行截图:




一、引言:为什么要在 HarmonyOS 上做口语陪练
随着 HarmonyOS 5 / 6 的全面铺开,鸿蒙生态已经走过了"能不能用"的阶段,进入了"好不好用"的阶段。越来越多的开发者开始把目光从 Android 与 iOS 转向 HarmonyOS ArkUI(ArkTS)开发。但在实际开发中,初学者往往会被几个共性问题卡住:ArkTS 到底和 Flutter、SwiftUI、Jetpack Compose 有什么不同?Grid、List 这些"老朋友"在 ArkUI 里要怎么写才对?状态管理怎么做?为什么我用 @State 修饰的变量明明赋值了但界面不刷新?页面之间怎么跳转、数据怎么传、参数怎么回传?
本项目「21 天语言训练营」正是为了解决这些疑问而生的。它只用最朴素的需求(语言学习)、最常见的控件(Grid 与 List)、最基础的 API(@State、@Builder、router),就完成了一个可以真机运行、商业级别的口语陪练 App。整个 App 的核心场景非常清晰:第一,闪卡背诵(Grid)。用两列网格展示短语与单词闪卡,点击翻转查看翻译、音标、例句。第二,AI 对话记录(List)。用列表展示历史 AI 对话练习的结果,包括话题、角色、时长、得分。第三,学习概览(顶部数据看板)。展示已学单词、练习次数、连续打卡天数等关键指标。
麻雀虽小,五脏俱全。下面我们就一步步拆解它,让你从"看得懂"到"写得出来"再到"写得漂亮"。
二、项目背景与产品定位
2.1 业务背景
在英语学习中,"短时高频 + 间隔重复"是被验证最有效的记忆方式。把这个理论映射到产品形态上,就是闪卡(Flashcard)系统。同时,"真实场景对话"是语言学习中提升流利度最高效的手段,但真人外教成本太高,AI 陪练成了最优解。因此,我们的产品形态是「闪卡打基础 + AI 对话做实战」。前者负责知识的输入与巩固,后者负责能力的输出与检验。
从用户旅程上看,一个典型的学习闭环是这样的:早晨醒来,打开 App → 复习 10 张闪卡(Grid)→ 完成 1 轮 AI 口语对话(List 中新增一条记录)→ 看到自己的得分与连续打卡天数增加 → 获得成就感 → 第二天继续。21 天后,用户养成习惯,付费意愿随之产生。
2.2 项目目标
技术上,本项目完整演示 ArkTS 中 Grid、List、@State、@Builder、router 等核心 API 的正确用法,让初学者看完就能复刻。体验上,界面简洁现代,操作流畅,闪卡翻转有即时反馈,对话记录一目了然。可扩展上,数据模型独立、UI 与逻辑解耦,方便后续接入真实 AI 接口、支付系统、用户体系。
2.3 目标用户
本项目的目标用户有三类。第一类,鸿蒙初学者:想找一个完整、可跑、可学的开源 ArkTS 项目作为入门参照。第二类,语言学习产品经理:想看一个可落地的原型,验证商业模式与转化漏斗。第三类,转型开发者:以前做 Android / iOS / 前端,想快速理解 ArkUI 的声明式思维模型。
2.4 为什么不选跨平台方案
你可能会问:既然 Flutter、React Native 都能跨端,为什么还要用 ArkTS?答案很简单:原生体验、性能优势、鸿蒙生态绑定。ArkTS 是 HarmonyOS 的"一等公民",它能直接调用所有鸿蒙原生能力,比如 CoreSpeechKit 语音识别、AI 引擎、原子化服务、跨设备流转等。这些能力用跨平台框架调用,要么绕弯子,要么根本调不到。从长期来看,鸿蒙生态的开发者红利只会越来越大,提前布局 ArkTS 是非常划算的。
三、技术选型与开发环境
3.1 技术栈
| 维度 | 选型 | 原因 |
|---|---|---|
| 框架 | ArkUI(ArkTS) | HarmonyOS 官方 UI 框架,声明式 |
| 语言 | ArkTS | TypeScript 超集,鸿蒙原生支持 |
| IDE | DevEco Studio | 官方 IDE,配套模拟器与真机调试 |
| SDK | 6.1.1(24) | 与 build-profile.json5 对齐 |
| 目标设备 | phone | 模块 deviceTypes 中声明 |
| 路由 | @kit.ArkUI 中的 router | 官方页面跳转能力 |
| 状态管理 | @State | 简单场景足够,避免引入过度设计 |
3.2 为什么选 ArkTS 而不是其他
ArkTS 的优势可以归纳为四点。第一,声明式。和 SwiftUI、Flutter、Jetpack Compose 一脉相承,学习成本低,思维模型一致。第二,强类型。TypeScript 基础,编译期就能发现大量错误,IDE 智能提示完善。第三,生态完整。官方控件库丰富,文档详尽,社区成长快。第四,性能好。基于方舟编译器,运行效率优于大多数跨平台方案。
3.3 开发环境准备
第一步,下载 DevEco Studio。建议使用 5.0 以上版本,对应 HarmonyOS 5 与 6。第二步,安装 SDK。在 DevEco Studio 中通过 SDK Manager 安装 6.1.1(24) 版本的 API。第三步,准备模拟器或真机。模拟器适合前期快速调试,真机适合后期验证性能与体验。第四步,Node.js。DevEco Studio 自带 Node 运行时,无需额外配置。
3.4 项目配置文件说明
build-profile.json5 关键字段定义了产品名 default、目标 SDK 6.1.1(24)、兼容 SDK 6.1.1(24)、运行时 HarmonyOS、构建模式 debug 与 release。
oh-package.json5(项目根)的 modelVersion 是 6.1.1,devDependencies 包含 @ohos/hypium 1.0.25(用于单元测试)和 @ohos/hamock 1.0.0(用于 mock 数据)。
entry/oh-package.json5(模块)保持空依赖,所有 UI 能力来自 ArkUI 内置包,无需引入第三方依赖。
四、项目目录结构解析
整个项目遵循 HarmonyOS 标准的多模块结构。AppScope 目录存放应用级配置,包括 app.json5 和 resources 中的应用图标、启动图。entry 是主入口模块,包含 src/main/ets/entryability(入口 Ability)、entrybackupability(备份能力)、pages(页面文件)。src/main/module.json5 定义模块清单,src/main/resources 存放颜色、字符串、布局资源。
重点关注的几个目录与文件。entry/src/main/ets/pages/ 是所有页面文件,ArkTS 的页面必须以 .ets 结尾。entry/src/main/resources/base/profile/main_pages.json 注册所有可路由的页面,没有注册的页面无法通过 router 跳转。entry/src/main/module.json5 模块清单,定义入口 Ability、支持设备类型、应用图标等。AppScope/resources/base/element/string.json 应用名、应用描述等字符串资源。
这种结构的最大好处是关注点分离。Ability 负责生命周期,pages 负责 UI,resources 负责静态资源,profile 负责路由配置。即使项目规模扩大到几十个页面,结构依然清晰。
五、核心功能实现详解
5.1 入口页与路由跳转
入口页 Index.ets 是一个简洁的欢迎页,包含一个"进入语言学习"按钮,点击后跳转到主功能页 LanguageLearning.ets。完整代码如下。
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Column() {
Text('🌐 21 天语言训练营')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#1976D2')
.margin({ top: 80, bottom: 16 })
Text(this.message)
.id('HelloWorld')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.margin({ bottom: 40 })
.onClick(() => {
this.message = 'Welcome';
})
Text('系统化训练 · 闪卡背诵 · AI 对话')
.fontSize(14)
.fontColor('#666666')
.margin({ bottom: 40 })
Button('进入语言学习')
.width('70%')
.height(48)
.fontSize(16)
.backgroundColor('#1976D2')
.onClick(() => {
router.pushUrl({ url: 'pages/LanguageLearning' });
})
}
.width('100%')
.height('100%')
.backgroundColor('#F5F7FA')
}
}
关键点解析。第一,@Entry 与 @Component 装饰器。@Entry 标记这是页面的入口,@Component 标记这是一个 UI 组件,二者缺一不可。第二,import router。从 @kit.ArkUI 中导入 router 模块,这是 ArkTS 官方提供的页面跳转能力。第三,router.pushUrl。跳转到 LanguageLearning 页面,url 是相对于 pages 目录的路径。第四,$r(‘app.float.xxx’)。通过资源 ID 引用资源文件中的常量,这是 ArkTS 推荐的做法。
main_pages.json 关键配置。
{
"src": [
"pages/Index",
"pages/LanguageLearning"
]
}
这里有个新手常踩的坑:忘记在 main_pages.json 中注册目标页面,导致 router.pushUrl 失败并报"url is not registered"错误。解决办法就是老老实实把所有页面都登记进来。
5.2 Grid 闪卡模块完整实现
闪卡模块是本项目的核心亮点之一,使用 ArkTS 的 Grid 加 GridItem 加 ForEach 实现两列网格布局,每张卡片支持点击翻转。
5.2.1 状态设计
@State flashcards: FlashCard[] = [
new FlashCard(1, 'Hello', '你好', '/həˈloʊ/', 'Hello, how are you?'),
new FlashCard(2, 'Thank you', '谢谢', '/θæŋk juː/', 'Thank you for your help.'),
new FlashCard(3, 'Goodbye', '再见', '/ɡʊdˈbaɪ/', 'Goodbye, see you tomorrow.'),
new FlashCard(4, 'Please', '请', '/pliːz/', 'Could you help me, please?'),
new FlashCard(5, 'Sorry', '对不起', '/ˈsɒri/', 'I am sorry for being late.'),
new FlashCard(6, 'Apple', '苹果', '/ˈæp.əl/', 'I eat an apple every day.'),
];
@State flippedIds: number[] = [];
@State selectedTab: number = 0;
关键点解析。flippedIds 用 number[] 而不是 Set:ArkTS 中 @State 对 Set 的响应式支持不完善,concat 与 filter 返回新数组的方式更可靠。selectedTab 控制当前显示哪个 Tab 页签,0 表示闪卡,1 表示对话记录。isFlipped(id) 辅助方法判断某张卡片是否已翻转。
isFlipped(id: number): boolean {
return this.flippedIds.indexOf(id) >= 0;
}
5.2.2 Grid 布局完整实现
@Builder
FlashCardGrid() {
Grid() {
ForEach(this.flashcards, (item: FlashCard) => {
GridItem() {
Column() {
if (this.isFlipped(item.id)) {
// 背面:显示释义和例句
Text(item.translation)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1976D2')
Text(item.phonetic)
.fontSize(13)
.fontColor('#888888')
.margin({ top: 6 })
Text(item.example)
.fontSize(12)
.fontColor('#555555')
.margin({ top: 10 })
.maxLines(3)
.textOverflow({ overflow: TextOverflow.Ellipsis })
} else {
// 正面:显示单词
Text(item.word)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text('点击查看释义')
.fontSize(12)
.fontColor('#AAAAAA')
.margin({ top: 10 })
}
}
.width('100%')
.height(140)
.padding(14)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(() => {
if (this.isFlipped(item.id)) {
this.flippedIds = this.flippedIds.filter((v: number) => v !== item.id);
} else {
this.flippedIds = this.flippedIds.concat([item.id]);
}
})
}
}, (item: FlashCard) => item.id.toString())
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.padding(16)
.layoutWeight(1)
}
关键点解析。@Builder 装饰器把 UI 片段抽成可复用函数,FlashCardGrid() 是实例方法而非全局函数,所以需要通过 this.FlashCardGrid() 调用。columnsTemplate(‘1fr 1fr’) 定义两列等宽网格,fr 是 fraction 单位的简写,表示"等分"。columnsGap(12) 与 rowsGap(12) 设置列间距和行间距,让卡片之间有合适的呼吸感。ForEach 的第二个参数是 key 生成器,第三个参数 (item: FlashCard) => item.id.toString() 必须提供且唯一,用于优化列表渲染。
点击翻转时用 filter 与 concat 返回新数组,这是 ArkTS 触发 @State 响应式刷新的标准做法。如果直接调用 splice 或 push 修改原数组,UI 不会更新,这是 ArkTS 初学者最容易踩的坑之一。
5.3 List 对话记录模块完整实现
List 模块展示 AI 对话练习的历史记录,每条记录包含话题、角色、时长、得分等信息。
5.3.1 数据结构定义
class ConversationRecord {
id: number = 0;
topic: string = '';
partner: string = '';
duration: string = '';
score: number = 0;
timeText: string = '';
status: string = '';
constructor(id: number, topic: string, partner: string, duration: string, score: number, timeText: string, status: string) {
this.id = id;
this.topic = topic;
this.partner = partner;
this.duration = duration;
this.score = score;
this.timeText = timeText;
this.status = status;
}
}
字段说明:id 是唯一标识,用于 ForEach 的 key 生成;topic 是对话主题(如"餐厅点餐");partner 是 AI 角色名称(如"AI 店员 Emma");duration 是对话时长(如"08:32");score 是 AI 给出的评分(0 到 100);timeText 是相对时间描述(如"今天 09:15");status 是等级描述(如"优秀")。
5.3.2 List 布局完整实现
@Builder
ConversationList() {
List() {
ForEach(this.conversations, (item: ConversationRecord) => {
ListItem() {
Row() {
// 左侧头像圆
Column() {
Text(item.partner.substring(item.partner.length - 1))
.fontSize(20)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.width(48)
.height(48)
.borderRadius(24)
.backgroundColor(this.StatusColor(item.score))
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
// 中间信息
Column() {
Text(item.topic)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Row() {
Text(item.partner)
.fontSize(12)
.fontColor('#888888')
Text(' · ')
.fontSize(12)
.fontColor('#CCCCCC')
Text(item.duration)
.fontSize(12)
.fontColor('#888888')
}
.margin({ top: 4 })
Text(item.timeText)
.fontSize(11)
.fontColor('#AAAAAA')
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
// 右侧得分
Column() {
Text(item.score.toString())
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(this.StatusColor(item.score))
Text(item.status)
.fontSize(11)
.fontColor('#888888')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
.padding(14)
.backgroundColor('#FFFFFF')
.borderRadius(10)
}
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
}, (item: ConversationRecord) => item.id.toString())
}
.layoutWeight(1)
.backgroundColor('#F5F7FA')
.listDirection(Axis.Vertical)
.divider({ strokeWidth: 0 })
}
关键点解析。List 加 ListItem 加 ForEach 是 ArkTS 中列表渲染的标准三件套。列表项内部用 Row 加三个 Column 实现"左头像加中信息加右得分"的经典布局。layoutWeight(1) 让中间列占据剩余空间,实现自适应布局。divider({ strokeWidth: 0 }) 隐藏默认分割线,因为我们用了 borderRadius 的卡片样式。listDirection(Axis.Vertical) 明确指定列表方向(默认值就是垂直,但显式声明更清晰)。
5.4 Tab 切换的两种实现
页面顶部有两个 Tab:“短语闪卡"和"AI 对话记录”,通过 @State selectedTab 控制显示内容。本项目用的是 if 条件渲染的方式,简单直接。
Row() {
Text('短语闪卡')
.layoutWeight(1)
.fontSize(15)
.fontWeight(this.selectedTab === 0 ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.selectedTab === 0 ? '#1976D2' : '#666666')
.padding(10)
.onClick(() => {
this.selectedTab = 0;
})
Text('AI 对话记录')
.layoutWeight(1)
.fontSize(15)
.fontWeight(this.selectedTab === 1 ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.selectedTab === 1 ? '#1976D2' : '#666666')
.padding(10)
.onClick(() => {
this.selectedTab = 1;
})
}
.width('100%')
.border({ width: { bottom: 1 }, color: '#EEEEEE' })
// 内容区
if (this.selectedTab === 0) {
this.FlashCardGrid()
} else {
this.ConversationList()
}
关键点解析。通过三元运算符动态改变文字的 fontWeight 和 fontColor,实现简单的 Tab 高亮效果。用 if 语句控制内容区显示,比 visibility 属性更彻底(不会占用布局空间)。如果想做成滑动切换效果,可以用 Tabs 组件,但本项目用 if 切换更轻量,代码也更易读。
ArkTS 也提供了原生的 Tabs 组件,它支持顶部导航、侧边导航、底部导航三种样式,还能配合 TabContent 实现滑动切换。如果你的 Tab 数量较多(≥3 个)或者需要更丰富的交互动画,建议升级到 Tabs 组件。
六、数据模型设计最佳实践
6.1 FlashCard 模型
class FlashCard {
id: number = 0;
word: string = '';
translation: string = '';
phonetic: string = '';
example: string = '';
mastered: boolean = false;
constructor(id: number, word: string, translation: string, phonetic: string, example: string) {
this.id = id;
this.word = word;
this.translation = translation;
this.phonetic = phonetic;
this.example = example;
}
}
字段说明:id 是唯一标识,用于 ForEach 的 key 生成;word 是单词或短语本体(英文);translation 是中文翻译;phonetic 是音标(IPA);example 是例句,展示单词在真实语境中的用法;mastered 是是否已掌握(预留字段,后续可接入间隔重复算法)。
6.2 ConversationRecord 模型
该模型在 5.3.1 节已经详述,这里不再重复。
6.3 模型设计的四条最佳实践
第一,类比接口更实用。在 ArkTS 中,类比接口更适合做数据模型,因为类可以有默认值和构造函数,UI 中可以直接 new 使用,无需额外写工厂函数。第二,字段默认值。所有字段都给出默认值(如 id: number = 0),避免 ArkTS 严格模式下的空值检查报错。第三,构造器封装。用 constructor 封装字段初始化逻辑,调用方更简洁。第四,预留扩展点。mastered 字段就是预留的,后续接入 SM-2 或 Anki 算法时直接用,不需要再改模型。
为什么不用 interface?在 ArkTS 严格模式下,interface 不能作为运行时类型,实例化时无法保证字段被正确赋值。而 class 既是类型也是值,编译器会强制要求所有字段被初始化,运行时也支持 instanceof 检查,更适合业务开发。
七、UI 设计思路与色彩系统
7.1 色彩系统
本项目采用了一套简洁的三色系统。主色 #1976D2 用于标题、按钮、强调文字,是 Material Design 蓝的微调版本,稳重而不失活力。成功色 #4CAF50 用于高分(≥90)以及"已学单词"指标,传递积极信号。信息色 #2196F3 用于中高分(80 到 89)以及"练习次数"指标,传达中性信息。警告色 #FF9800 用于中分(60 到 79)以及"连续打卡"指标,提示需要加强。危险色 #F44336 用于低分(小于 60),警示用户需要重新练习。背景色 #F5F7FA 作为页面底色,比纯白柔和。卡片色 #FFFFFF 作为闪卡、列表项背景。次要文字 #888888 用于辅助信息。弱化文字 #AAAAAA 用于占位提示。
7.2 排版规范
标题字号 22 到 28pt,FontWeight.Bold。正文字号 14 到 16pt,FontWeight.Normal。辅助文字 11 到 13pt,FontWeight.Normal。卡片高度闪卡固定 140pt,确保两列对齐。间距外边距 16pt,卡片间距 12pt,元素内边距 14pt。
7.3 圆角与阴影
卡片圆角 10 到 12pt,柔和现代。头像圆角 24pt(等于宽度的一半,等于完美圆形)。按钮默认圆角,宽度 70%,高度 48pt,符合人体工程学的手指点击热区。
7.4 设计原则四条
第一,留白充足。每张卡片之间有 12pt 间距,页面四周有 16pt 内边距,避免视觉拥挤。第二,信息层级清晰。用字号、字重、颜色三重区分主次信息,让用户一眼抓住重点。第三,反馈即时。点击闪卡立即翻转,点击 Tab 立即切换状态,不让用户产生"操作失灵"的疑虑。第四,一致的视觉语言。所有卡片用相同的圆角、背景色、内边距,强化品牌识别。
7.5 鸿蒙设计规范建议
如果你想更专业,建议参考 HarmonyOS Design 规范,使用官方推荐的字体(HarmonyOS Sans)、间距栅格、色彩系统。这不仅能提升产品观感,还能在应用市场上获得官方推荐。
八、关键技术细节深度解析
8.1 卡片翻转状态管理的三个坑
在 ArkTS 中,状态管理有几个"坑"必须注意,否则代码看起来对,但 UI 就是不刷新。
坑 1:直接修改对象不触发刷新
// 错误:直接修改 @State 对象的内部状态
@State card: FlashCard = new FlashCard(1, 'Hello', '你好', '/həˈloʊ/', 'Hello, how are you?');
changeCard() {
this.card.word = 'Hi'; // UI 不会刷新!
}
原因:ArkTS 的 @State 装饰器只监听引用变化,不监听对象内部属性变化。解决方案:创建新对象。
// 正确:创建新对象
changeCard() {
this.card = new FlashCard(1, 'Hi', '你好', '/haɪ/', 'Hi, how are you?');
}
坑 2:Set 类型的响应式支持不完善
// 不推荐
@State flippedIds: Set<number> = new Set();
flip(id: number) {
this.flippedIds.add(id); // UI 可能不刷新
}
解决方案:用数组加 filter 加 concat。
// 推荐:用数组 + filter/concat
@State flippedIds: number[] = [];
flip(id: number) {
if (this.isFlipped(id)) {
this.flippedIds = this.flippedIds.filter((v: number) => v !== id);
} else {
this.flippedIds = this.flippedIds.concat([id]);
}
}
坑 3:@Builder 函数的调用方式
ArkTS 的 @Builder 装饰器有两种用法。第一,全局 @Builder,定义在组件外,用 BuilderName() 调用。第二,组件内 @Builder,定义在 struct 内,需要用 this.BuilderName() 调用。本项目用的是第二种。
@Component
struct MyComponent {
@Builder
MyBuilder() {
Text('Hello')
}
build() {
Column() {
this.MyBuilder() // 通过 this 调用
}
}
}
8.2 评分颜色映射函数
StatusColor(score: number): string {
if (score >= 90) {
return '#4CAF50'; // 优秀 - 绿色
} else if (score >= 80) {
return '#2196F3'; // 良好 - 蓝色
} else if (score >= 60) {
return '#FF9800'; // 及格 - 橙色
} else {
return '#F44336'; // 不及格 - 红色
}
}
设计思路:用红绿灯式的颜色映射,让用户一眼看出自己表现。阈值 90、80、60 是常见的评分体系分界线。颜色与顶部"学习概览"的三个指标色保持一致,强化视觉品牌。
这个函数同时被用在两个地方:左侧头像的背景色和右侧得分的文字色,形成视觉呼应。
8.3 列表项布局的 layoutWeight 技巧
对话记录列表项是典型的"左中右"三栏布局,关键技巧就是 layoutWeight。
Row() {
// 左:固定宽度头像
Column() { ... }
.width(48)
.height(48)
// 中:占据剩余空间
Column() { ... }
.layoutWeight(1) // 关键:占据剩余空间
.margin({ left: 12 })
// 右:自适应宽度
Column() { ... }
.alignItems(HorizontalAlign.End)
}
layoutWeight(1) 是 ArkTS 中实现"中间自适应"的核心 API,它告诉布局引擎"把所有剩余空间分配给这个组件"。当左右两栏固定宽度时,中间栏自动填满剩余空间,无论屏幕宽度如何变化。
8.4 ForEach 的 key 生成器
ForEach(this.flashcards, (item: FlashCard) => {
// 渲染逻辑
}, (item: FlashCard) => item.id.toString())
第三个参数的作用:当数据源变化时,ArkTS 用这个 key 来判断哪些项是新增的、哪些是删除的、哪些是移动的,从而最小化 UI 刷新范围。最佳实践:key 必须是稳定且唯一的(用 id 而非 index);key 必须是字符串(toString() 一下);不要用 Math.random() 或时间戳,否则在数据变化时会导致整个列表重建。
如果数据量超过 100 条,建议用 LazyForEach 替代 ForEach。LazyForEach 会只渲染可见区域,大幅提升性能。
8.5 string.json 多语言资源化
将所有用户可见的文本放入 resources/base/element/string.json,方便后续 i18n。
{
"string": [
{
"name": "module_desc",
"value": "语言学习"
},
{
"name": "EntryAbility_desc",
"value": "21 天语言训练营"
},
{
"name": "EntryAbility_label",
"value": "语言学习"
}
]
}
使用时通过 $r(‘app.string.module_desc’) 引用。当未来要出海时,只需要新增 en_US、ja_JP 等目录,把字符串翻译一遍即可。
九、性能优化与最佳实践
9.1 减少不必要的渲染
9.1.1 合理拆分组件
如果一个组件包含复杂的子树,建议拆分成独立的 @Component,这样当父组件刷新时,子组件可以通过 @Prop 或 @Link 精确控制刷新范围,避免整个树重新渲染。
9.1.2 避免内联对象
// 不推荐:每次 build 都创建新对象
ForEach(this.items, (item: Item) => {
ListItem() {
Text(item.name)
.fontSize({ size: 16, weight: FontWeight.Bold } as any) // 性能差
}
})
// 推荐:提取常量
private static readonly TITLE_STYLE: FontWeight = FontWeight.Bold;
9.2 列表性能优化
9.2.1 使用 LazyForEach 替代 ForEach
对于超长列表(大于 100 项),LazyForEach 会只渲染可见区域,大幅提升性能。本项目数据量小(6 张闪卡、5 条记录),用 ForEach 即可。
9.2.2 避免在循环中做重计算
// 不推荐
ForEach(this.items, (item: Item) => {
Text(this.heavyCompute(item)) // 每次 build 都重算
})
// 推荐:缓存计算结果
computedMap: Map<string, string> = new Map();
heavyCompute(item: Item): string {
if (!this.computedMap.has(item.id)) {
this.computedMap.set(item.id, /* 重计算 */);
}
return this.computedMap.get(item.id)!;
}
9.3 状态管理进阶
对于更复杂的项目,建议引入 @Provide 与 @Consume 实现跨组件层级共享状态;@Observed 与 @ObjectLink 监听嵌套对象变化;@StorageLink 与 @StorageProp 实现全局持久化状态。但对于本项目(数据量小、状态简单),@State 已经足够。
什么时候该升级到全局状态管理?当多个页面需要共享同一份数据时(如用户信息、学习进度),或者数据需要跨页面持久化时(如登录态、设置项),就适合引入 AppStorage 或 LocalStorage。
9.4 资源管理
9.4.1 颜色资源化
将常用颜色放入 resources/base/element/color.json:
{
"color": [
{ "name": "brand_primary", "value": "#1976D2" },
{ "name": "success", "value": "#4CAF50" }
]
}
使用时通过 $r(‘app.color.brand_primary’) 引用。
9.4.2 字符串资源化
本项目已经将 module_desc、EntryAbility_desc 等放入 string.json,方便后续多语言适配。
9.4.3 图片资源化
图标、插画等图片放入 resources/base/media/,通过 $r(‘app.media.xxx’) 引用,避免硬编码路径。
9.5 启动性能优化
第一,避免在 aboutToAppear 中做重操作。页面生命周期回调 aboutToAppear 中不要做同步的耗时操作(如读取大文件、网络请求),否则会阻塞首屏渲染。第二,懒加载非首屏内容。主页面只需要渲染首屏可见的部分,其他内容可以用 LazyForEach 懒加载。第三,预编译模板。DevEco Studio 默认开启了 AOT 预编译,无需额外配置。
十、常见问题与解决方案
Q1:点击按钮没反应,路由跳转失败?
原因:目标页面未在 main_pages.json 中注册。解决:在 main_pages.json 的 src 数组中添加目标页面路径。
Q2:@State 修改了,UI 没刷新?
原因:直接修改对象内部属性或使用了 Set 或 Map。解决:用 concat、filter 或创建新对象的方式触发引用变化。
Q3:Grid 列宽不均?
原因:columnsTemplate 写错。解决:
.columnsTemplate('1fr 1fr') // 两列等宽
.columnsTemplate('repeat(2, 1fr)') // 等价写法
Q4:List 列表项之间有空隙?
原因:默认 divider 有高度。解决:
List() { ... }
.divider({ strokeWidth: 0 }) // 隐藏分割线
Q5:ForEach 报警告"the array may be updated during rendering"?
原因:在 ForEach 内部修改了被遍历的数组。解决:把修改操作放到 onClick 等事件回调中,避免在 build 阶段修改数据。
Q6:模拟器运行正常,真机崩溃?
原因:可能缺少权限或资源。解决:第一步,检查 module.json5 中的 requestPermissions。第二步,检查资源文件是否全部打包。第三步,查看真机日志 hdc shell hilog。
Q7:如何实现页面间参数传递?
通过 router.getParams() 在目标页面接收参数。
// 发送方
router.pushUrl({ url: 'pages/Detail', params: { id: 123 } });
// 接收方
@State id: number = 0;
aboutToAppear() {
const params = router.getParams() as Record<string, Object>;
this.id = params['id'] as number;
}
Q8:如何实现返回上一页并刷新数据?
使用 router.back() 后,在上一页的 aboutToAppear 中重新加载数据。
// 上一页
aboutToAppear() {
this.loadData();
}
// 当前页
router.back();
十一、扩展方向与未来演进
本项目为最小可运行版本(MVP),后续可从以下几个方向扩展。
11.1 数据持久化
闪卡数据可以用 @ohos.data.preferences 存到本地,后续可同步到云端。学习记录每次 AI 对话结束后追加到 conversations 数组,再写入 Preferences。学习概览根据历史数据动态计算,不写死。
进阶方案是用 @ohos.data.relational.store 关系型数据库存储,支持 SQL 查询,更适合复杂业务。
11.2 AI 能力接入
真实 AI 对话可以用 @kit.NetworkKit 调用 LLM API(如 DeepSeek、ChatGLM、Qwen),实现真正的口语陪练。语音识别用 @kit.CoreSpeechKit 识别用户发音。语音评测用 @kit.CoreSpeechKit 的发音评估接口给用户打分,提供更客观的反馈。
11.3 学习算法
间隔重复集成 SM-2 或 Anki 算法,自动安排复习时间。艾宾浩斯曲线根据遗忘曲线动态调整闪卡出现频率。学习路径根据用户水平动态调整单词难度,循序渐进。
11.4 UI 增强
动画方面给闪卡翻转加 transition 动画,提升体验。暗黑模式适配深色主题,跟随系统设置自动切换。横屏适配用媒体查询做响应式布局,在不同设备上都有良好体验。
11.5 社交化
学习圈让用户分享学习成果。排行榜和好友比拼学习天数,激发竞争意识。小组学习组队打卡,互相监督,提升留存率。
11.6 商业化
会员体系分级权益(基础免费、Pro 会员解锁高级功能)。付费课程包提供雅思、托福、商务英语等专项训练。硬件联动与华为耳机、智能音箱联动,实现"随时随地学"。
十二、总结与展望
12.1 项目价值
「21 天语言训练营」是一个麻雀虽小、五脏俱全的 HarmonyOS ArkTS 教学项目。它用最朴素的控件(Grid、List)、最基础的状态管理(@State)、最简单的路由(router)实现了一个完整的语言学习 App。
对于 ArkTS 初学者来说,本项目的价值在于:完整性,从入口页到主功能页,从数据模型到 UI 渲染,完整演示了一个真实 App 的结构;可运行性,所有代码都经过验证,可以直接在 DevEco Studio 中跑起来;可扩展性,数据模型独立、UI 与逻辑解耦,方便后续接入真实 AI 能力。
12.2 技术收获
通过本项目,你应该掌握了以下技能。第一,ArkTS 项目结构,知道每个目录、每个配置文件的作用。第二,Grid、List 控件,理解它们的属性、用法、性能特点。第三,@State 状态管理,知道什么时候用数组、什么时候用对象、什么时候需要拆组件。第四,router 路由,掌握页面跳转的完整流程。第五,@Builder UI 抽离,学会把复杂 UI 拆成可复用片段。第六,数据模型设计,理解类、构造函数、默认值的用法。
12.3 学习建议
如果你想进一步深入 ArkTS 开发,建议按以下路径学习。第一步,官方文档,访问 HarmonyOS 开发者官网,查阅 ArkTS 与 ArkUI 的权威指南。第二步,ArkTS 语言规范,重点看类型系统与装饰器,这是 ArkTS 与 TypeScript 的关键差异。第三步,ArkUI 组件库,熟悉所有内置组件的属性与事件。第四步,状态管理 V2,学习新一代状态管理方案 @ObservedV2、@Trace。第五步,实战项目,从仿写成熟 App 开始,逐步做原创。
12.4 写在最后
HarmonyOS 生态正在快速发展,ArkTS 作为官方推荐语言,未来几年会有大量的人才需求。希望本项目能成为你鸿蒙之旅的起点,也欢迎你在此基础上做出更有创意的产品。21 天养成一个习惯,21 天入门一门新技术。愿我们都能在鸿蒙生态中找到自己的位置。
项目地址:d:\Code\Learn\ArkTS\21DayHarmonyOSTraining\ArkTSSpoken
核心页面:entry/src/main/ets/pages/LanguageLearning.ets
技术栈:ArkTS 6.1.1(24) + ArkUI + HarmonyOS
如果本文对你有帮助,欢迎点赞、收藏、评论!你的支持是我持续输出优质内容的最大动力。后续我还会写更多 ArkTS 实战教程,包括但不限于:状态管理 V2 详解、网络请求封装、原子化服务开发、跨设备流转等。敬请期待。
十三、HarmonyOS 工具链与调试技巧详解
13.1 DevEco Studio 高效开发技巧
第一,快捷键配置。DevEco Studio 基于 IntelliJ IDEA,快捷键与 WebStorm、PyCharm 一致。建议熟练掌握以下快捷键:双击 Shift 打开全局搜索、Ctrl 加 Shift 加 F 全局查找、Ctrl 加 Shift 加 R 全局替换、Alt 加 Enter 万能修复、Ctrl 加 Alt 加 L 格式化代码、Ctrl 加 / 单行注释、Ctrl 加 Shift 加 / 块注释。掌握这些快捷键能让开发效率提升至少三成。
第二,实时预览。在编辑器右上角点击 Previewer 按钮,可以打开实时预览面板,修改代码后立即看到 UI 变化,无需反复编译运行。预览面板支持切换设备型号、暗黑模式、字体大小,非常适合 UI 调优。
第三,Inspector 抓取 UI 结构。运行应用后,在 DevEco Studio 的 Hilog 中可以看到 UI 树结构。配合 ui-viewer 工具,可以抓取真实运行时的 UI 层级,方便排查布局问题。
第四,hdc 命令行调试。hdc(HarmonyOS Device Connector)是鸿蒙的设备连接工具,类似于 Android 的 adb。常用命令包括 hdc list targets 列出连接的设备、hdc shell 进入设备 shell、hdc install 安装应用、hdc file send 发送文件到设备、hdc hilog 抓取设备日志、hdc shell screencap 截屏。
13.2 常见编译错误的解决方法
错误 1:Cannot find name ‘X’。原因:变量未声明或导入缺失。解决:检查 import 语句,必要时补全类型声明。
错误 2:Type ‘X’ is not assignable to type ‘Y’。原因:类型不匹配。解决:检查函数签名与实际调用,必要时做类型转换或调整函数定义。
错误 3:Property ‘X’ does not exist on type ‘Y’。原因:访问了对象不存在的属性。解决:检查对象模型,添加缺失字段或修正属性名。
错误 4:Strict mode violation。ArkTS 默认开启严格模式,禁止使用 any、联合类型等动态类型。解决:使用具体类型或 Object 替代 any。
13.3 真机调试全流程
第一步,开启开发者模式。在手机设置 → 关于手机 → 连续点击版本号 7 次,进入开发者模式。第二步,开启 USB 调试。在开发者选项中开启 USB 调试开关。第三步,连接设备。用 USB 数据线连接手机和电脑,授权调试。第四步,选择设备。在 DevEco Studio 顶部设备选择栏中,选中已连接的真机。第五步,运行项目。点击 Run 按钮(绿色三角),应用就会安装到真机上并启动。
真机调试比模拟器更能反映真实性能,特别是涉及动画、列表滚动、网络请求等场景。建议在功能基本稳定后,及时在真机上验证体验。
十四、ArkTS 与主流跨端框架的对比
很多开发者在选型时会纠结:到底用 ArkTS、Flutter、React Native 还是 KMP(Kotlin Multiplatform)?下面从多个维度做一次系统对比。
14.1 语言与生态
ArkTS 是 TypeScript 超集,语法对前端开发者非常友好,学习成本低。Flutter 用 Dart 语言,生态集中在 Google 体系。React Native 用 JavaScript/TypeScript,生态最丰富但碎片化。KMP 用 Kotlin,主打共享业务逻辑,UI 层仍需各自实现。
14.2 性能表现
ArkTS 基于方舟编译器,原生渲染,性能最优。Flutter 自绘引擎,60fps 流畅,但在低端机上偶有掉帧。React Native 通过 Bridge 与原生通信,复杂动画时性能较差。KMP UI 层依赖原生,性能接近原生。
14.3 鸿蒙生态适配
ArkTS 是一等公民,所有鸿蒙原生能力(原子化服务、跨设备流转、AI 引擎、语音识别)直接调用,无需任何桥接。Flutter 适配鸿蒙依赖社区插件,部分能力受限。React Native 适配鸿蒙还处于早期阶段。KMP 可以通过 expect/actual 机制调用鸿蒙原生能力,但门槛较高。
14.4 学习成本
ArkTS 对前端开发者最友好,对 Android 开发者次之。Flutter 对新手友好,但需要学 Dart。React Native 对前端开发者最友好。KMP 对 Kotlin 开发者最友好。
14.5 商业化前景
从长期看,HarmonyOS 设备保有量会持续增长,ArkTS 人才缺口会持续扩大。Flutter 适合需要同时支持 Android 和 iOS 的场景。React Native 适合已有 JS 技术栈的团队。KMP 适合大型企业级项目需要最大化代码复用。
14.6 选型建议
如果你的产品只在 HarmonyOS 上跑,强烈推荐 ArkTS。如果需要同时支持 Android 和 iOS,Flutter 是更稳妥的选择。如果团队前端背景强,可以考虑 React Native。如果追求代码复用极致,可以研究 KMP。但从鸿蒙生态绑定、未来增长性、政策支持等角度看,ArkTS 是最值得投入的方向。
十五、商业化落地与产品迭代建议
15.1 MVP 阶段:从 0 到 1
MVP(Minimum Viable Product)阶段的核心是快速验证产品假设。建议的开发顺序是:第一周完成闪卡背诵模块,第二周完成 AI 对话基础框架,第三周接入真实 AI 接口,第四周打磨 UI 与体验。MVP 阶段不要追求功能完备,只关注核心闭环是否跑通。
15.2 增长阶段:从 1 到 10
增长阶段的核心是用户留存与裂变。关键指标包括:日活、月活、次日留存、7 日留存、30 日留存、付费转化率。增长抓手包括:连续打卡激励机制、分享得会员功能、学习数据可视化、AI 对话场景多样化。推荐通过 A/B 测试驱动迭代,每两周一次小版本发布。
15.3 商业化阶段:从 10 到 100
商业化阶段的核心是变现效率与用户满意度平衡。商业化路径包括:免费基础功能加付费 Pro 会员、按课程包计费、与硬件厂商联合会员、广告变现(但学习 App 慎用广告)。定价策略上,参考市面同类产品,年卡定价在 168 元到 298 元区间较为合理。
15.4 团队配置建议
MVP 阶段 2 到 3 人即可,1 名全栈 + 1 名 UI 设计 + 1 名产品。增长阶段需要扩充到 5 到 8 人,增加 AI 算法工程师、后端开发、增长运营。商业化阶段 10 人以上,需要补齐数据分析师、市场、客服等岗位。
15.5 风险与应对
技术风险:AI 接口不稳定。应对:建立降级方案,AI 不可用时使用预设对话。政策风险:内容合规、未成年人保护。应对:接入内容审核服务,建立敏感词库。竞争风险:巨头入场。应对:聚焦垂直场景(如儿童英语、商务英语),建立差异化壁垒。
十六、开发者成长路径建议
16.1 入门期(0 到 3 个月)
第一,熟读官方文档,把 ArkTS 基础语法过一遍。第二,动手复刻本项目,从零开始写一遍,加深理解。第三,阅读 3 到 5 个高质量的开源 ArkTS 项目,学习优秀实践。第四,尝试修改本项目,添加一些新功能(如收藏、搜索、统计)。
16.2 进阶期(3 到 12 个月)
第一,学习状态管理 V2(@ObservedV2、@Trace)。第二,学习网络请求(@ohos.net.http、@kit.NetworkKit)。第三,学习持久化(@ohos.data.preferences、@ohos.data.relational.store)。第四,尝试做一个完整的原创项目,从需求分析到上线运营全流程跑通。
16.3 高级期(1 年以上)
第一,学习原子化服务开发,让应用可以免安装使用。第二,学习跨设备流转,实现手机/平板/智慧屏无缝切换。第三,学习 Stage 模型与 FA 模型的差异,理解 HarmonyOS 的应用架构演进。第四,参与开源项目,贡献代码,建立个人品牌。
16.4 推荐学习资源
官方文档(developer.huawei.com)是第一手资料,质量最高。HarmonyOS 开发者社区(developer.huawei.com/consumer/cn/forum)有大量实战问答。GitHub 上搜索 arkts harmonyos 可以找到很多优秀开源项目。B 站、慕课网、极客时间上也有不少 ArkTS 视频教程。
16.5 心态建议
学习一门新技术最重要的是"开始动手",而不是"准备充分"。本项目就是一个很好的起点,不要被"我基础还不够好"的想法困住。代码是写出来的,不是想出来的。先跑起来,再优化;先实现,再完美。鸿蒙生态正在爆发期,现在入场正是最好的时机。
十七、附录:完整项目文件清单
为了方便读者快速浏览与复刻本项目,下面列出所有关键文件及其作用。
根目录文件。build-profile.json5:项目级构建配置。oh-package.json5:项目级包管理配置。hvigorfile.ts:构建脚本入口。code-linter.json5:代码检查规则。
AppScope 目录。app.json5:应用级配置(应用名、版本号、图标)。resources/base/element/string.json:应用名、描述等字符串资源。
entry 模块。src/main/module.json5:模块清单,定义入口 Ability 与设备类型。src/main/ets/entryability/EntryAbility.ets:Ability 生命周期入口。src/main/ets/pages/Index.ets:欢迎页与路由跳转入口。src/main/ets/pages/LanguageLearning.ets:主功能页(Grid 闪卡 + List 对话记录)。src/main/resources/base/profile/main_pages.json:页面路由注册。src/main/resources/base/element/string.json:模块字符串资源。src/main/resources/base/element/color.json:颜色资源。src/main/resources/base/element/float.json:尺寸资源。
测试相关。src/ohosTest/ets/test/Ability.test.ets:Ability 端到端测试。src/ohosTest/ets/test/List.test.ets:列表组件测试。src/test/LocalUnit.test.ets:本地单元测试。src/test/List.test.ets:列表单元测试。src/mock/mock-config.json5:测试 mock 数据配置。
每个文件都在 HarmonyOS 应用中扮演特定角色,缺一不可。理解这些文件的用途,能帮你快速定位问题,也为后续扩展打下基础。
十八、写在最后的话
HarmonyOS 代表着下一代操作系统的方向。ArkTS 是这个生态的官方语言,掌握它不仅是技术能力的提升,更是搭上了鸿蒙生态的快车。语言学习是一个长青赛道,把 ArkTS 能力与语言学习产品结合,既能打磨技术,又能创造真实价值,这正是本项目最大的意义。
希望本文不仅是一篇教程,更是一份启发。如果你读完之后产生了"我也想做一个自己的 ArkTS 项目"的冲动,那么本项目就完成了它的使命。
21 天入门 ArkTS,21 天养成学习习惯。从今天开始,从一行代码开始,从你的第一个 Grid 控件开始。
愿每一位读者都能在 HarmonyOS 生态中找到自己的位置,做出有价值的产品,收获属于自己的成就感。鸿蒙生态,未来可期。让我们一起砥砺前行,共同成长。
更多推荐


所有评论(0)