
《HarmonyOS技术精讲-UI开发》第1篇:初识ArkTS与声明式UI
开篇:为什么需要重新学"写UI"?
如果你刚开始接触 HarmonyOS 开发,第一个要面对的挑战不是 API 有多复杂,而是 ArkTS 这套声明式 UI 写法跟 Android 的 XML 或 iOS 的 Storyboard 完全是两个思路。
很多从传统移动端转过来的开发者,第一反应是:"我在代码里写 UI 界面?这不乱套了?"
实际体验下来,ArkTS 的声明式写法在状态管理和 UI 刷新上确实更直接 ,但你得先理解它的核心规则:@Component、@Entry 和 build() 这三个东西是怎么配合的。
这篇文章的目标很简单:用 Hello World 上手,让你明白 ArkTS 声明式 UI 到底怎么组织代码,并且知道第一版代码里最容易踩哪些坑。
环境说明
text
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:手机
本文不涉及版本兼容问题,但建议使用最新稳定版 IDE。
它解决什么问题:用代码"描述"界面,而非"创建"界面
声明式 vs 命令式:一次对比
命令式(传统方式):你一步步告诉程序"创建按钮"、"设置颜色"、"添加到屏幕"。
声明式(ArkTS 方式):你直接描述"界面上有一个按钮,背景为白色,字为黑色",框架自己管怎么渲染和刷新。
| 特性 | 命令式 (Android View) | 声明式 (ArkTS / SwiftUI) |
|---|---|---|
| 如何定义 UI | 手动创建 + 设置属性 + addView | 描述结构 + 绑定状态 |
| 状态更新 | 手动 findView + setText | 状态变量变化 → UI 自动刷新 |
| 代码结构 | 多个文件(XML + java) | 单文件(结构 + 逻辑) |
| 调试复杂度 | 中(需关注生命周期) | 低(状态驱动) |
ArkTS 声明式开发范式 适合的场景:需要频繁更新 UI、页面状态比较复杂、团队人数多的项目。不适合:对渲染性能极度敏感、手写大量 Canvas 绘制的场景(但 ArkUI 对这类需求的性能优化也不错,后续文章会讨论)。
核心概念:组件、装饰器、构建函数
@Component:标记一个 struct 为 UI 组件。每个组件就是一个界面部件,可以嵌套。@Entry:标记一个组件为页面入口。没有@Entry,DevEco Studio 不会把它当作独立页面来运行。build():描述组件长什么样、什么行为。这是声明式 UI 的心脏,所有 UI 代码必须写在这里面。
一句话总结:
@Entry标记页面,@Component定义组件,build()画界面。
核心实现:创建一个 Hello World 项目
第一步:新建项目
打开 DevEco Studio,选择"Empty Ability"模板。项目名称随意,包名保持默认。

第二步:编写页面代码
创建完项目后,在 entry/src/main/ets/pages 目录下找到 Index.ets。
这是最基础的页面文件结构:
typescript
// Index.ets
@Entry
@Component
struct HelloWorldPage {
build() {
Column() {
Text('Hello, HarmonyOS!')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
Button('点击我')
.onClick(() => {
console.info('按钮被点击')
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
代码说明:
@Entry告诉系统这是一个页面入口,可以被路由跳转或作为启动页。@Component声明一个组件类。struct HelloWorldPage是组件名,可以自由命名,但建议语义化。build()内部只能有一个根组件(这里是Column),这是 ArkUI 的硬性规定。
注意事项:
build()里不能有控制语句(如if-else)?可以,但要 包裹在build()内部。ArkTS 4.0 之后支持条件语句,但仅限于判断组件是否显示,不能用于创建组件实例。- 每个组件必须声明
build()方法,且返回值类型必须是void。 Column和Row是最常见的布局容器,相当于 HTML 的 flex 布局。
第三步:预览效果
点击 DevEco Studio 右上角的 Previewer 按钮,或者连接真机/模拟器,点击运行。

如果一切正常,你会看到屏幕中央显示"Hello, HarmonyOS!"和一个按钮。
常见问题与避坑(实战经验)
开发 ArkTS 声明式 UI,前几次一定会遇到下面两个问题。
问题 1:@Entry 与 @Component 的顺序写反了
现象 :页面无法被路由找到,预览器报错 @Entry is not a decorator。
原因 :@Entry 必须写在 @Component 前面,顺序不能颠倒。这不是语法错误,但 ArkUI 的编译期扫描会按顺序解析。如果你不小心写成了:
typescript
@Component
@Entry // 错误:@Entry 必须在前
struct DemoPage {}
IDE 会直接标红,不算隐晦错误,但新手容易忽略。
解决方案 :保持 @Entry 在最上面一行,其次是 @Component。
问题 2:build() 方法里不能写业务逻辑
现象 :页面上有一个复杂的列表,你直接在 build() 里循环创建组件,运行后页面卡顿甚至闪退。
原因 :ArkUI 的渲染引擎在每次状态变化时都会重新调用 build() 来重建组件树。如果你在 build() 里做耗时操作(如大量循环、网络请求),UI 线程会被阻塞,导致 ANR(应用无响应)。
解决方案 :把数据准备逻辑放在 aboutToAppear() 或 onPageShow() 中,build() 只负责定义界面。
typescript
@Entry
@Component
struct GoodDesign {
@State dataList: string[] = []
aboutToAppear() {
// 在这里做数据初始化,而不是 build() 里
this.dataList = ['Item1', 'Item2', 'Item3']
}
build() {
List() {
ForEach(this.dataList, (item: string) => {
ListItem() {
Text(item)
}
})
}
}
}
最佳实践
-
每个
@Entry页面只有一个根组件 。这是 ArkUI 的规则,不要尝试在
build()里写两个并列的<Column>或<Row>,否则编译报错。如果需要并列布局,用Stack或Flex包裹。 -
保持良好的命名习惯 。
组件名用大写驼峰(
HelloWorldPage、MainContent),文件命名用小写字母 + 连字符(hello-world-page.ets)。项目混着写太多风格,后面重构会很痛苦。 -
避免在
build()中频繁创建对象 。例如
let colors = ['red', 'blue']; this.colors.forEach(...)。每次build()都会创建新对象,ArkUI 的 diff 算法可能认为组件树完全变化,导致无谓重渲染。推荐把数据放到@State或@Observed中统一管理。 -
善用 Previewer 快速验证 UI 逻辑 。
Previewer 不需要真机,启动很快,适合快速调样式。但要注意,部分原生 API(如定位、相机)在 Previewer 中会失效,仍然需要真机测试。
Demo 入口
以下是完整项目入口文件:
typescript
// entry/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
build() {
Column() {
Text('Hello, ArkTS!')
.fontSize(30)
Button('点击跳转')
.onClick(() => {
router.pushUrl({ url: 'pages/SecondPage' })
})
}
}
}
FAQ(真实开发视角)
Q1:为什么我修改了 @State 变量的值,UI 没有刷新?
A1:最常见原因是你修改的是一个对象属性 ,但 ArkTS 的 @State 只能监听到最顶层引用变化 。比如 @State user = {name: 'Alice'},你只改了 this.user.name = 'Bob',ArkUI 检测不到。解决方案:使用 @Observed 装饰类,或者直接替换整个对象(this.user = {name: 'Bob'})。后续文章会详细讲状态管理。
Q2:页面返回后,之前输入的内容丢了,怎么办?
A2:默认情况下,页面的 @State 变量在页面销毁后会被清除。如果你希望保留状态(如表单数据),需要将状态提升到父组件,或者使用 LocalStorage / AppStorage。对于简单场景,可以先用 @Prop / @Link 从父组件传参。
Q3:为什么 Previewer 显示正常,真机运行就布局变形?
A3:Previewer 的屏幕尺寸和真机差异很大。很多新手喜欢在代码里写 height: '100%',但真机下如果父容器没有明确高度,百分比可能会解析失败。推荐使用 flex 布局或单位 vp / fp,避免依赖百分比。
Q4:build() 方法里能不能写循环或条件?
A4:可以,但要谨慎。ArkTS 4.0 支持 if 和 ForEach,但要注意控制循环体不要有复杂逻辑。官方推荐把复杂条件判断抽到 @State 变量中,然后在 build() 里直接用。
总之,ArkTS 声明式开发范式 上手成本在于理解"声明式"思维,而不是 API 本身。只要记住 @Entry 管页面,@Component 管组件,build() 只管描述界面,第一周就不会走偏。