简介:这份资源面向鸿蒙应用开发初学者与转型开发者,提供鸿蒙5.0(HarmonyOS NEXT)平台App开发的入门级完整工程示例,帮助读者在真实项目结构中理解从初始化到部署的开发流程。压缩包共445个文件,约1.84MB,以js、json、ts、json5、ets等为主:ets与js承载页面与逻辑代码,json/json5负责包管理、构建配置与代码规范,ts用于构建脚本,另有少量png、md、xml等资源与说明文件,并包含一个hap产物便于对照。工程内配置覆盖代码格式化、版本控制忽略规则、依赖锁定、构建参数与代码检查等环节,AppScope等目录则展示应用源码与资源的组织方式。目前已有454人学习下载,适合希望快速跑通鸿蒙5.0开发链路、熟悉工程目录与配置体系的入门者参考实践。
1. 从零跑通鸿蒙 5.0 应用:一份能直接上手的入门 Demo 拆解
如果你最近在找 HarmonyOS 鸿蒙 next 的入门路径,大概率会陷入两种困境:要么是官方文档太全,全到不知道从哪一页开始动手;要么是网上碎片化的帖子,贴了几行 ArkTS 代码就没了下文,连工程怎么建、跑在哪、报错怎么查都没说清。这份「鸿蒙 5.0 系统 app 开发入门 Demo」解决的就是这个断层——它不是文档搬运,而是一个能让你在半小时内看到界面的最小可运行工程。适合谁?有前端或移动端基础、想快速摸清鸿蒙 next 开发范式的从业者;也适合完全没碰过 ArkTS 但愿意照着步骤敲一遍的新手。它不教你做完整产品,但能让你把「工程结构、页面路由、状态管理、组件通信」这条主线跑通,后面再往上叠业务就有了骨架。
2. 环境搭建与工程结构:把第一个页面跑起来
2.1 工具链选型:为什么是 DevEco Studio 而不是别的
鸿蒙 next 的应用开发,官方主推的 IDE 就是 DevEco Studio。常见做法是直接去官网下对应操作系统的安装包,但这里有个容易被忽略的点:DevEco Studio 的版本必须和 SDK 版本匹配。我一般会先确认目标设备的系统版本,再倒推该装哪个版本的 IDE。比如你要跑在鸿蒙 5.0 的设备或模拟器上,IDE 版本就不能太旧,否则编译时会出现 SDK 组件缺失的报错,而且这种报错往往只给一个模糊的组件名,新手很容易卡在这里。
安装过程中有两个勾选项值得注意:一是「HarmonyOS SDK」的安装路径,建议不要放在中文目录或带空格的路径下,不然后续 Gradle 同步阶段可能出玄学问题;二是「Node.js」和「ohpm」的自动配置,让 IDE 自己装就行,手动指定反而容易版本冲突。装完之后第一次启动会提示你登录开发者账号,这一步不是必须的,但登录后能同步一些模板和文档,建议登一下。
工程创建时,模板选择「Empty Ability」,语言选 ArkTS,设备类型勾选 Phone 和 Tablet 都行。创建完成后你会看到一个标准目录结构,核心的几个目录我列一下:
| 目录/文件 | 作用 | 新手容易忽略的点 |
|---|---|---|
| entry/src/main/ets | 存放 ArkTS 源码 | 页面和组件都在这,别放错到 resources |
| entry/src/main/ets/pages | 页面文件目录 | 每个页面一个 .ets 文件,路由靠它 |
| entry/src/main/module.json5 | 模块配置 | 页面路由注册、权限声明都在这 |
| entry/src/main/resources | 资源文件 | 字符串、图片、颜色统一管理 |
| build-profile.json5 | 构建配置 | 签名、编译版本在这改 |
2.2 第一个页面:ArkTS 声明式 UI 的最小闭环
建好工程后,默认会生成一个 Index.ets,里面已经有一个简单的「Hello World」页面。但直接看默认代码容易懵,我习惯把它清空,从最小结构写起。下面这段代码是一个带按钮和状态变化的页面,能让你直观感受到 ArkTS 的声明式写法:
// Index.ets @Entry @Component struct Index { // @State 修饰的变量变化时会触发 UI 刷新 @State message: string = '鸿蒙 5.0 入门' @State count: number = 0 build() { Column({ space: 20 }) { // 文本组件,绑定状态变量 Text(this.message) .fontSize(28) .fontWeight(FontWeight.Bold) Text(`点击次数:${this.count}`) .fontSize(18) .fontColor('#666666') // 按钮组件,点击时修改状态 Button('点我 +1') .width(160) .height(44) .onClick(() => { this.count += 1 // 根据次数切换提示文案 this.message = this.count >= 5 ? '你已经点了不少次了' : '鸿蒙 5.0 入门' }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }这段代码的逻辑很直白:@Entry标记这是页面入口,@Component表示这是一个自定义组件,struct里定义状态和 UI。@State是 ArkTS 状态管理的核心装饰器,它修饰的变量一旦变化,框架会自动重新执行build()里依赖该变量的部分,不需要你手动操作 DOM。Column是纵向布局容器,space: 20表示子元素间距 20vp。Button的onClick里直接改this.count,UI 就会跟着变。
参数上,fontSize单位默认是 fp,width/height默认是 vp,这两个单位在鸿蒙里是分开的,fp 会随系统字体大小缩放,vp 不会。如果你发现文字在不同设备上大小不一致,先检查是不是用错了单位。justifyContent(FlexAlign.Center)控制主轴对齐,Column 的主轴是纵向,所以这里是垂直居中。
写完保存,点 IDE 右上角的运行按钮,选模拟器或真机,等编译安装完成就能看到界面。第一次编译会下载依赖,时间取决于网络,耐心等就行。
2.3 页面路由:从单页到多页的跳转
单页跑通后,下一步就是多页跳转。鸿蒙 next 的路由有两种常见方式:一种是在module.json5里注册页面路径,然后用router跳转;另一种是用Navigation组件做更复杂的导航栈管理。入门阶段先用router就够了。
先在pages目录下新建一个Detail.ets,内容简单写个文本加返回按钮:
// Detail.ets @Entry @Component struct Detail { build() { Column({ space: 16 }) { Text('这是详情页') .fontSize(24) Button('返回') .onClick(() => { // 返回上一页 router.back() }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }然后在module.json5的pages字段里加上"pages/Detail",注意路径不要带.ets后缀。接着在 Index 页面的按钮里加跳转:
import { router } from '@kit.ArkUI' // 在 Index 的 onClick 里 router.pushUrl({ url: 'pages/Detail', params: { from: 'Index' } // 可选参数,详情页用 router.getParams() 取 })这里有个坑:router.pushUrl的url必须和module.json5里注册的路径完全一致,大小写敏感。如果跳转没反应,先检查这个。另外params传参在页面间传递对象时,注意不要传太复杂的数据结构,入门阶段传字符串和数字最稳。
3. 状态管理与组件通信:让数据在页面间正确流动
3.1 @State、@Prop、@Link 的区别与选用场景
ArkTS 的状态管理装饰器有好几个,新手最容易混的就是@State、@Prop、@Link。我用一个表格把它们的核心差异说清楚:
| 装饰器 | 数据流向 | 是否支持修改 | 典型场景 |
|---|---|---|---|
| @State | 组件内部 | 是 | 页面自己的状态 |
| @Prop | 父到子单向 | 子组件可改但不回传 | 父组件传值给子组件展示 |
| @Link | 父子双向 | 是,双向同步 | 子组件需要修改父组件状态 |
@State你已经见过了,它管的是组件自己的状态。@Prop是父组件传给子组件的,子组件可以改,但改了不会影响父组件,相当于一份拷贝。@Link则是双向绑定,子组件改了父组件也会变。
我一般这样判断:如果子组件只是展示数据,用@Prop;如果子组件需要反过来改父组件的状态,用@Link。举个例子,父组件有个计数器,子组件要能加也能减,那就用@Link。
// 子组件 @Component struct CounterChild { @Link count: number // 双向绑定 build() { Row({ space: 12 }) { Button('-').onClick(() => { this.count -= 1 }) Text(`${this.count}`).fontSize(20) Button('+').onClick(() => { this.count += 1 }) } } } // 父组件中使用 @Entry @Component struct ParentPage { @State total: number = 0 build() { Column({ space: 20 }) { Text(`父组件看到的值:${this.total}`) CounterChild({ count: this.total }) // 用 $ 符号传递引用 } } }注意父组件传值给@Link时,要用$前缀,比如count: this.total要写成count: $total,这是语法要求,漏了会编译报错。这个报错信息有时候不太直观,新手容易找不到原因。
3.2 组件生命周期:aboutToAppear 与 aboutToDisappear
每个 ArkTS 组件都有生命周期回调,入门阶段最常用的是aboutToAppear和aboutToDisappear。前者在组件即将显示时触发,适合做数据初始化;后者在组件销毁时触发,适合做资源释放。
@Entry @Component struct LifecycleDemo { @State data: string = '加载中...' aboutToAppear() { // 模拟异步加载 setTimeout(() => { this.data = '数据加载完成' }, 1000) } aboutToDisappear() { // 清理定时器、取消订阅等 console.info('页面即将销毁') } build() { Column() { Text(this.data).fontSize(20) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }这里有个血泪经验:aboutToAppear里如果做异步操作,要注意组件可能已经销毁但回调还在执行,导致对已销毁组件操作状态而报错。常见做法是在aboutToDisappear里设一个标志位,或者在异步回调里判断组件是否还存活。入门 Demo 里数据简单可能遇不到,但一旦接真实接口,这个问题几乎必现。
3.3 父子组件通信的完整示例
把上面的知识点串起来,写一个稍微完整点的例子:父组件维护一个列表,子组件负责展示和删除某一项。
// 子组件:列表项 @Component struct TodoItem { @Prop index: number @Prop content: string @Link items: string[] // 双向绑定,删除时直接操作数组 build() { Row({ space: 12 }) { Text(`${this.index + 1}. ${this.content}`) .fontSize(16) .layoutWeight(1) Button('删除') .fontSize(14) .onClick(() => { // 从数组中移除当前项 this.items.splice(this.index, 1) }) } .width('100%') .padding(12) .backgroundColor('#F5F5F5') .borderRadius(8) } } // 父组件 @Entry @Component struct TodoPage { @State items: string[] = ['学习 ArkTS 语法', '理解状态管理', '跑通页面路由'] build() { Column({ space: 12 }) { Text('待办清单').fontSize(24).fontWeight(FontWeight.Bold) ForEach(this.items, (item: string, index: number) => { TodoItem({ index: index, content: item, items: $items }) }, (item: string) => item) // key 生成函数,保证唯一性 } .width('100%') .padding(16) } }ForEach是列表渲染的核心组件,第一个参数是数据源,第二个是生成子组件的函数,第三个是 key 生成函数。key 必须唯一且稳定,否则列表更新时会出现错乱。这里用字符串本身做 key,实际项目里建议用唯一 id。@Link传递数组时用$items,子组件里splice操作会直接反映到父组件的@State上,UI 自动刷新。
4. 避坑与排查:新手最容易翻车的五个地方
4.1 编译报错「SDK component missing」
现象:点运行后编译失败,提示某个 SDK 组件找不到。原因通常是 IDE 版本和 SDK 版本不匹配,或者安装时 SDK 没下全。解决:打开 DevEco Studio 的 SDK Manager,检查 HarmonyOS SDK 下对应 API 版本的组件是否都勾选了,缺哪个补哪个。如果补了还报错,尝试 File -> Invalidate Caches 清缓存重启。
4.2 页面跳转白屏或没反应
现象:调了router.pushUrl但页面没跳,或者跳过去是白屏。原因有两个常见可能:一是module.json5里没注册目标页面路径,二是路径大小写或拼写不一致。解决:先检查module.json5的pages数组,确认路径和pushUrl里的url完全一致。白屏的话,检查目标页面是否有@Entry装饰器,漏了就不会被当作页面渲染。
4.3 @Link 传值编译报错
现象:父组件传值给子组件的@Link变量时,编译提示类型不匹配或语法错误。原因:@Link要求父组件用$前缀传递引用,写成count: this.total是错的,必须写count: $total。解决:检查所有@Link的传参处,补上$。这个错误 IDE 有时不会高亮提示,只能靠编译输出定位。
4.4 状态改了但 UI 不刷新
现象:明明改了@State变量的值,但界面没变化。原因:可能是改的不是被@State修饰的变量,或者改的是对象/数组的内部属性但引用没变。ArkTS 的状态刷新依赖引用变化,直接改obj.name有时不触发。解决:对对象用展开运算符生成新对象,比如this.obj = { ...this.obj, name: '新值' };对数组用concat、slice等返回新数组的方法,或者用splice这类会改变原数组且框架能感知的操作。
4.5 模拟器运行卡顿或安装失败
现象:模拟器启动慢,或者应用安装到模拟器时报错。原因:模拟器对机器配置有要求,内存不足时容易卡;安装失败可能是签名配置问题。解决:入门阶段用真机调试往往比模拟器更顺,真机开启开发者模式后 USB 连接即可。如果必须用模拟器,确保分配的内存不低于 4GB,并且签名用 IDE 自动生成的调试签名,不要手动改。
5. 进阶技巧:用 Previewer 和日志把调试效率提上来
5.1 善用 Previewer 实时预览
DevEco Studio 自带 Previewer,不用跑模拟器就能看 UI 效果。在.ets文件右上角点 Previewer 按钮,右侧会弹出预览窗口。改代码后保存,预览会实时刷新。这个功能在调布局和样式时特别省时间,我一般写页面时全程开着 Previewer,只有涉及路由跳转和设备能力时才切到模拟器或真机。
Previewer 有个限制:它不执行真正的生命周期和异步逻辑,aboutToAppear里的setTimeout在预览里可能不触发。所以数据加载类的逻辑还是要在真机上验证,Previewer 只用来调静态 UI。
5.2 日志输出与过滤
ArkTS 里用console.info、console.error输出日志,在 IDE 的 Log 窗口能看到。但日志多了之后很难找,我习惯在关键位置加统一前缀,比如console.info('[TodoPage] items count:', this.items.length),然后在 Log 窗口的过滤框里输入[TodoPage]就能只看这个页面的日志。
另外hilog是鸿蒙原生的日志系统,比console更底层,适合在 Ability 生命周期里用。入门阶段console够用,等涉及后台任务或服务卡片时再切hilog。
5.3 一个我常做的验证习惯
每次改完状态管理相关的代码,我不会只看界面变没变,而是会在build()里临时加一行console.info打印当前状态值,确认数据流的方向和预期一致。因为 UI 有时候会因为缓存或刷新时机看起来「对了」,但实际数据流是错的,后面接业务逻辑就会出问题。这个习惯帮我省了很多回头排查的时间。
从那以后我每次新建鸿蒙工程,都会先把module.json5的页面注册、@Link的$传参、ForEach的 key 这三处过一遍,确认没漏再开始写业务。希望帮到你。
本文还有配套的精品资源,点击获取