# 🎯 小白学鸿蒙 ArkUI 全记录:跟着百万博主从零手写 TodoApp 的完整心路历程

---

## 一、这篇教程在教什么?

先说说这个 Tutorial 的背景。博主的文章是一个**待办事项(Todo List)应用教程**,用纯 ArkTS + ArkUI 实现。

我当时在想:**Todo 应用也太简单了吧?** 但跟着写完之后我才明白,它包含了 ArkUI 开发的 **所有核心概念**:

| 核心概念 | 在 Todo 里对应什么 |
|---------|------------------|
| 声明式 UI | `@Component` 描述界面长什么样 |
| 状态管理 | `@State` 让数据变了 UI 自动刷新 |
| 常用组件 | `TextInput`(输入框)、`Checkbox`(勾选框)、`Button` |
| 布局 | `Column`(纵向)、`Row`(横向)、`List`(列表) |
| 列表渲染 | `ForEach` 循环渲染任务列表 |
| 事件处理 | 点击添加、勾选完成、点击删除 |
| 动画 | `animateTo` 让增删过渡更丝滑 |
| 数据筛选 | 按"全部/进行中/已完成"过滤 |
| 数据统计 | 实时显示总数和完成数 |

一句话总结:**别看 Todo 小,五脏俱全。** 通过它你能搞懂 ArkUI 90% 的日常开发场景。

---

## 二、我的环境搭建过程

### 2.1 下载 DevEco Studio

第一关:**装开发工具**。

我去华为开发者官网下载了 **DevEco Studio 5.0**(最新版),安装包大概 1.2 GB,下载了大概 10 分钟。

> 📸 **步骤说明**:
> ```
> [步骤 1] 打开华为开发者联盟官网 → 找到 DevEco Studio 下载页
> [步骤 2] 选择 Windows 版本 → 点击下载
> [步骤 3] 双击安装包 → 一路 Next → 安装完成
> ```

### 2.2 配置 SDK(我卡得最久的一步)

装完 DevEco Studio,第一件事是配置 SDK。这里我**踩了个大坑**——

第一次打开 IDE,它会自动提示下载 SDK,但下载进度条走到一半就停了,我以为是卡住了,直接关掉了窗口。结果后面怎么都编译不通过。

**后来才知道**:SDK 下载不能中断,而且最好挂个代理或者晚上下载(速度会快一些)。

正确的 SDK 配置步骤:

1. 打开 DevEco Studio → 点击 **Configure** → **Settings**
2. 搜索 **SDK** → 选择 HarmonyOS SDK 路径
3. 勾选 **API 23(HarmonyOS NEXT)** → 点击 Apply
4. 等待下载完成(大约需要 15-30 分钟,看你网速)

> 📸 **SDK 配置说明**:
> ```
> - 左侧导航:Appearance & Behavior → System Settings → HarmonyOS SDK
> - 右侧:勾选 API 23 → Apply 按钮
> ```

### 2.3 创建项目

SDK 装好后,创建项目:

```
1. 点击 "Create HarmonyOS Project"
2. 选模板:选择 "Empty Ability"(空的页面模板)
3. 项目信息:
   - Project Name: TodoApp
   - Bundle Name: com.example.todoapp
   - Save Location: 自己选一个文件夹
4. 选 API 版本:API 23(ArkTS)
5. 点击 Finish
```

> 📸 **创建项目说明**:
> ```
> Create Project 界面
> - 模板选择 Empty Ability
> - 项目名称输入 TodoApp
> - API 版本选择 23
> ```

第一次创建项目,IDE 会下载一些依赖,等待右下角的进度条走完就行。

### 2.4 认识项目结构(小白必看!)

项目创建完成后,左侧会出现这样的目录结构(我当时看了半天才明白):

```
TodoApp/
├── AppScope/                          # 应用配置
│   └── app.json5                      # 应用名字、图标等
├── entry/                             # 主模块(我们的代码在下面)
│   └── src/main/
│       ├── ets/                       # ★ ArkTS 源代码(重点!)
│       │   ├── entryability/          # 应用入口(生命周期)
│       │   └── pages/                 # 页面文件夹
│       │       └── Index.ets          # ★ 我们要改的文件
│       └── resources/                 # 图片、字符串等资源
├── build-profile.json5                # 构建配置
├── oh-package.json5                   # 依赖管理
└── hvigorfile.ts                      # 构建脚本
```

**新手只需要记住一件事:** 我们 99% 的代码都写在 `entry/src/main/ets/pages/Index.ets` 这个文件里。

> **💡 我的理解**:`.ets` 就是 ArkTS 文件的后缀名,相当于前端的 `.js` 或 `.tsx`,是鸿蒙原生开发的代码文件。

---

## 三、从零开始写代码(跟着博主一步步来)

### 3.1 第一步:定义数据结构 —— 认识 interface

打开 `Index.ets`,发现里面已经有了一些示例代码,但我们要全部删掉重写。

博主的第一步是定义**待办事项的数据结构**:

```typescript
// 定义待办事项的数据结构
interface TodoItem {
  id: number;         // 唯一标识 —— 区分不同的任务
  text: string;       // 任务内容 —— 用户输入的文字
  completed: boolean; // 是否完成 —— true=已完成,false=未完成
  createdAt: string;  // 创建时间 —— 记录什么时候创建的
}
```

> **🤔 我的理解**:`interface` 就是"数据模板"。它告诉程序:"每个待办事项都必须有 id、text、completed、createdAt 这四个字段"。
>
> 想象一下,它就像**考试答题卡的填涂框**—— 规定了每个位置填什么内容,填错了就识别不了。`number` 是数字,`string` 是文本,`boolean` 是 true/false。

**小白的额外发现**:`?` 可以标记可选属性,比如:

```typescript
interface TodoItem {
  id: number;
  text: string;
  completed: boolean;
  createdAt: string;
  priority?: number;   // 问号表示"可以有,也可以没有"
  tags?: string[];     // 可选的标签列表
}
```

这样定义后,每个待办事项可以有自己的优先级,但不是必须的。

---

### 3.2 第二步:创建组件 —— 认识 @Component 和 @State

接下来是最重要的一步:**创建主组件**。

```typescript
@Entry                          // 入口标记(这个页面是应用首页)
@Component                      // 声明这是一个组件
struct TodoApp {                // 组件名叫 TodoApp
  @State todos: TodoItem[] = [];     // 所有待办事项
  @State newTodoText: string = '';   // 输入框的文字
  @State nextId: number = 1;         // 下一个任务的 ID
  @State filter: number = 0;         // 筛选条件:0-全部 1-进行中 2-已完成

  build() {
    // 这里写界面
  }
}
```

> **🚨 我踩的坑**:我第一次写的时候,把 `@State` 写成了 `@state`(小写 s),结果编译直接报错。**装饰器必须严格区分大小写!**
>
> 还有一次我忘了写 `@Entry`,结果模拟器黑屏。`@Entry` 是告诉系统"该从哪个页面启动"。

**@State 到底是什么?**

博主文章里花了很大篇幅解释 @State,我一开始没看懂,后来用了一个比喻才明白:

> **@State 就像一个"智能监控摄像头"** —— 它盯着变量的值,一旦值变了,它就大喊"来人啊!值变了!",然后 ArkUI 框架就会重新渲染界面。

所以当你勾选一个复选框时,`todos[index].completed` 变成了 `true`,@State 检测到变化,UI 自动刷新,文字出现了删除线。

**这就是"数据驱动 UI"的精髓 —— 你只需要改数据,UI 自己会变!**

---

### 3.3 第三步:搭页面布局 —— Column + Row + Text

博主的下一步是搭页面的整体框架。ArkUI 的布局主要用 **Column(纵向排列)** 和 **Row(横向排列)**。

我按照博主的代码,在 `build()` 方法中写:

```typescript
build() {
  Column() {                          // ← 最外层:垂直排列
    // 1. 标题
    Text('我的待办')
      .fontSize(28)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .textAlign(TextAlign.Start)
      .padding({ top: 20, bottom: 10 })

    // 2. 统计信息
    Text(this.getStatisticsText())   // 调用方法获取统计文字
      .fontSize(14)
      .fontColor('#999')
      .width('100%')
      .padding({ bottom: 15 })

    // 3. 输入区域(输入框 + 添加按钮)
    Row() {                          // ← Row:横向排列
      TextInput({
        placeholder: '请输入待办事项...',
        text: this.newTodoText
      })
        .width('85%')
        .height(48)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)
        .onChange((value: string) => {
          this.newTodoText = value;   // 输入变化时更新状态
        })
        .onSubmit(() => {
          this.addTodo();            // 按回车提交
        })

      Button('添加')
        .width('13%')
        .height(48)
        .backgroundColor('#6366F1')
        .borderRadius(8)
        .fontColor('#FFFFFF')
        .onClick(() => {
          this.addTodo();
        })
    }
    .width('100%')
    .padding({ bottom: 15 })

    // 4. 列表区域(后面补充)
    // 5. 底部按钮(后面补充)
  }
  .padding(20)
  .width('100%')
  .height('100%')
  .backgroundColor('#FFFFFF')
}
```

> **🤔 我的理解**:
>
> - `Column()` 相当于 HTML 的 flex-direction: column,里面的东西从上往下排
> - `Row()` 相当于 flex-direction: row,里面的东西从左往右排
> - `.width('100%')` 是链式调用,ArkUI 里几乎所有属性都能这样"点"出来
>
> **ArkUI 和 CSS 的一个关键区别**:ArkUI 是用**方法链**设置样式(`.fontSize(28).fontColor('#333')`),而 CSS 是写键值对(`{font-size: 28px; color: #333}`)。

---

### 3.4 第四步:写业务逻辑 —— 增删改查

写完界面布局,接下来是实现核心功能。我按照博主给的代码,在 `build()` 方法外面添加了这些方法:

```typescript
// ===== 添加待办 =====
addTodo() {
  const text = this.newTodoText.trim();
  if (text === '') return;  // 空输入不处理

  this.todos.push({
    id: this.nextId++,
    text: text,
    completed: false,
    createdAt: new Date().toLocaleDateString()
  });
  this.newTodoText = '';  // 清空输入框
}

// ===== 切换完成状态 =====
toggleTodo(id: number) {
  const index = this.todos.findIndex(todo => todo.id === id);
  if (index >= 0) {
    this.todos[index].completed = !this.todos[index].completed;
  }
}

// ===== 删除待办 =====
deleteTodo(id: number) {
  this.todos = this.todos.filter(todo => todo.id !== id);
}

// ===== 获取筛选后的列表 =====
getFilteredTodos(): TodoItem[] {
  if (this.filter === 0) return this.todos;
  if (this.filter === 1) return this.todos.filter(todo => !todo.completed);
  return this.todos.filter(todo => todo.completed);
}

// ===== 统计信息 =====
getStatisticsText(): string {
  const total = this.todos.length;
  const completed = this.todos.filter(t => t.completed).length;
  return `共 ${total} 项,已完成 ${completed} 项`;
}

// ===== 清除已完成 =====
clearCompleted() {
  this.todos = this.todos.filter(todo => !todo.completed);
}
```

> **🚨 我踩的大坑(数组操作)**:
>
> 第一次写删除功能时,我用了 `this.todos.splice(index, 1)` —— 结果 UI 没刷新!
>
> **原因**:@State 装饰的数组,用 `push()` 添加能用是因为 ArkUI 对 `push` 做了特殊处理。但 `splice` 不行。删除必须用 **重新赋值** 的方式:
>
> ✅ 正确:`this.todos = this.todos.filter(todo => todo.id !== id)`
> ❌ 错误:`this.todos.splice(index, 1)`
>
> 这是新手最容易踩的坑之一,博主文章里专门强调了这一点,还好我看了。

---

### 3.5 第五步:用 @Builder 封装复用 UI

写到筛选标签的时候,我发现"全部""进行中""已完成"三个标签的样式很像,只是文字和值不同。

博主在这里用了 **@Builder** —— ArkUI 的 UI 复用装饰器:

```typescript
@Builder
createFilterTab(filterValue: number, label: string) {
  Text(label)
    .fontSize(14)
    .fontColor(this.filter === filterValue ? '#6366F1' : '#999')
    .fontWeight(this.filter === filterValue ? FontWeight.Bold : FontWeight.Normal)
    .padding({ top: 6, bottom: 6, left: 16, right: 16 })
    .backgroundColor(this.filter === filterValue ? '#EEF2FF' : '#F5F5F5')
    .borderRadius(16)
    .onClick(() => {
      this.filter = filterValue;
    })
}
```

然后在 `build()` 里这样调用:

```typescript
Row() {
  this.createFilterTab(0, '全部')
  this.createFilterTab(1, '进行中')
  this.createFilterTab(2, '已完成')
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
```

> **🤔 我的理解**:`@Builder` 就像定义了一个"UI 函数",传不同的参数就能生成不同的 UI。如果不用 `@Builder`,这三个标签的 15 行代码我要复制粘贴 3 次,那就是 45 行重复代码。`@Builder` 一下子缩减到了 15 行。

---

### 3.6 第六步:用 ForEach 渲染列表

接下来是最"爽"的一步 —— **让列表真正显示出来!**

```typescript
List({ space: 10 }) {        // space=10 是列表项间距
  ForEach(this.getFilteredTodos(), (todo: TodoItem) => {
    ListItem() {
      this.todoItemView(todo)  // 调用 @Builder 渲染每个待办
    }
  })
}
.width('100%')
.layoutWeight(1)  // 填充剩余空间
```

> **💡 特别注意**:博主的代码里用的是 `ForEach`(F大写),而不是 JavaScript 的 `forEach`(f小写)。两者区别很大:
>
> - `ForEach`(大写)是 ArkUI **组件**,用来渲染列表
> - `forEach`(小写)是 JavaScript **数组方法**,用来遍历数组
>
> 我第一次写错了,编译报错"找不到符号 ForEach"…… 检查了半天才发现是大写小写的问题。

结合 `@Builder` 写的待办项 UI:

```typescript
@Builder
todoItemView(todo: TodoItem) {
  Row() {
    Checkbox()
      .select(todo.completed)
      .selectedColor('#6366F1')
      .onChange(() => {
        this.toggleTodo(todo.id);
      })

    Text(todo.text)
      .fontSize(16)
      .fontColor(todo.completed ? '#CCC' : '#333')
      .decoration({
        type: todo.completed ?
          TextDecorationType.LineThrough : TextDecorationType.None
      })
      .layoutWeight(1)
      .padding({ left: 8 })

    Text(todo.createdAt)
      .fontSize(12)
      .fontColor('#CCC')

    Button() {
      Text('✕').fontSize(14).fontColor('#FF6B6B')
    }
    .backgroundColor('transparent')
    .width(30)
    .height(30)
    .onClick(() => {
      this.deleteTodo(todo.id);
    })
  }
  .width('100%')
  .padding(12)
  .backgroundColor('#FAFAFA')
  .borderRadius(10)
  .alignItems(VerticalAlign.Center)
}
```

**这里我学到了几个关键点:**

1. **Checkbox 搭配 `.select()` 控制选中状态** —— 不是用 `checked`,而是用 `select`
2. **`.decoration()` 实现文字删除线** —— 这是 ArkUI 的文本装饰,`LineThrough` 就是删除线效果
3. **三元表达式做条件样式** —— `todo.completed ? '#CCC' : '#333'` 是 ArkTS 的标准语法

---

### 3.7 第七步:加上动画效果

博主在文章最后加上了 `animateTo` 动画——这算是一个"加分项":

```typescript
// 添加时带动画
addTodo() {
  const text = this.newTodoText.trim();
  if (text === '') return;

  animateTo({ duration: 300 }, () => {
    this.todos.push({
      id: this.nextId++,
      text: text,
      completed: false,
      createdAt: new Date().toLocaleDateString()
    });
  });
  this.newTodoText = '';
}

// 删除时带动画
deleteTodo(id: number) {
  animateTo({ duration: 200 }, () => {
    this.todos = this.todos.filter(todo => todo.id !== id);
  });
}
```

> **🤔 我的理解**:`animateTo` 是 ArkUI 的显式动画 API。它的工作原理是:把状态变更放在回调函数里,ArkUI 会自动计算"变化前的 UI"到"变化后的 UI"之间的**差值**,然后在这两个状态之间**插帧过渡**。
>
> 比如删除一个任务时,`animateTo` 会让这个任务先有一个"淡出并缩小"的动画效果,而不是"啪"一下突然消失。用户体验会好很多。

---

### 3.8 运行成功的那一刻!

写完所有代码后,我点击了右上角的 **运行按钮(▶️)**,选择了 **P40 Pro 模拟器**。

等待大概 1 分钟(模拟器启动比较慢),屏幕上终于出现了我的 TodoApp!

> 📸 **运行成功截图说明**:
> ```
> [HUAWEI P40 Pro 模拟器截图]
> ┌─────────────────────────────────┐
> │  我的待办                        │
> │  共 3 项,已完成 1 项            │
> │                                 │
> │  [写今天的作业...         ] [添加]│
> │                                 │
> │  ○ 全部  ○ 进行中  ● 已完成     │
> │                                 │
> │  ☑ 学习 ArkUI      2025/01/15 ✕ │
> │  ☐ 写 TodoApp      2025/01/15 ✕ │
> │  ☐ 发布博客文章    2025/01/16 ✕ │
> │                                 │
> │  [    清除已完成    ]            │
> └─────────────────────────────────┘
> ```

**那一刻真的很有成就感!** 虽然只是一个简单的 TodoApp,但这是我亲手写了每一行代码、从 0 到 1 构建出来的应用。

我测试了所有功能:
- ✅ 输入文字 → 点击添加 → 任务出现
- ✅ 点击复选框 → 文字出现删除线
- ✅ 点击 ✕ 按钮 → 任务被删除(带动画)
- ✅ 点击筛选标签 → 只显示对应状态的任务
- ✅ 点击"清除已完成" → 所有完成的任务消失
- ✅ 统计信息实时更新

**全部工作正常!**

---

## 四、我踩过的坑汇总(新手避坑指南)

下面是我在学习过程中踩过的所有坑,**每一行都是血泪教训**:

| 分类 | 坑的描述 | 正确做法 | 我的血泪史 |
|:---:|---------|---------|-----------|
| 🚨 装饰器 | 把 `@State` 写成 `@state` | 装饰器首字母必须大写 | 编译报错了 10 分钟才找到原因 |
| 🚨 数组操作 | 用 `splice()` 删除元素 | 用 `filter()` 重新赋值 | 删了但 UI 不刷新 |
| 🚨 ForEach | 写成 `forEach`(小写) | 必须大写 `ForEach` | 编译报错"找不到符号" |
| 🚨 TextInput | 没传 `text` 参数 | `TextInput({text: this.text})` | 输入了但状态不更新 |
| 🚨 导包 | 忘写 import | ArkTS 自动导入 | 其实不用手动 import 组件 😅 |
| 🚨 模拟器 | SDK 下载中断 | 挂代理 + 一次性下完 | 项目构建失败,重装了两次 |
| 🚨 `build()` | 在 `build()` 里写业务逻辑 | `build()` 只写 UI | 代码一长就很难维护 |
| 🚨 颜色格式 | 写 `#FFF`(三位简写) | 必须写 `#FFFFFF`(六位) | 颜色不生效 |

---

## 五、学完这个项目后的核心收获

### 5.1 我理解的 ArkUI 声明式语法

学完 TodoApp,我对 ArkUI 最核心的 **声明式语法** 有了直观认识:

```
┌─────────────────────────────────────────────┐
│  声明式语法 = "描述结果,而不是描述过程"        │
│                                              │
│  传统写法(命令式):                          │
│    button.setColor('blue')                   │
│    label.setText('Hello')                    │
│    view.addChild(button)                     │
│                                              │
│  ArkUI(声明式):                            │
│    Button('Hello')                           │
│      .backgroundColor('blue')                │
│      .fontSize(16)                           │
│                                              │
│  区别:声明式直接描述"界面应该长什么样",        │
│  而不是"先创建按钮,再设置颜色,再添加到视图"    │
└─────────────────────────────────────────────┘
```

### 5.2 我理解的 @State 核心机制

```
用户操作(点击Checkbox)
      ↓
触发 onChange 事件
      ↓
更新 @State 变量(todos[index].completed = true)
      ↓
ArkUI 框架监听到状态变化
      ↓
重新执行 build() 生成新 UI
      ↓
界面自动刷新(文字出现删除线)
```

这就是 **"数据驱动 UI"** 的完整链路。我现在理解了为什么叫"驱动"—— 数据是发动机,UI 只是被带动的轮子。

### 5.3 我理解的常用组件关系

```
页面结构:
┌─ Column(纵向容器)──────┐
│  ├─ Text(标题)        │
│  ├─ Row(横向容器)      │
│  │  ├─ TextInput(输入) │
│  │  └─ Button(按钮)    │
│  ├─ Row(筛选栏)        │
│  │  └─ Text × 3(标签)  │
│  ├─ List(可滚动列表)    │
│  │  └─ ListItem × N     │
│  │     └─ Row            │
│  │        ├─ Checkbox    │
│  │        ├─ Text(内容) │
│  │        └─ Button(删) │
│  └─ Button(清除已完成)   │
└─────────────────────────┘
```

---

## 六、给其他小白的建议 + 下一步学习路线

### 6.1 给新手的三条真心建议

1️⃣ **不要怕报错,报错是老师**

我写这个项目至少报错了 20 次,但每次报错我都把错误信息复制到百度/CSDN搜一下,基本上都能找到答案。**错误信息是最好的学习材料。**

2️⃣ **先照抄,再理解,后创造**

我的学习路径是:先把博主的代码一字不差地敲一遍 → 跑起来了 → 试着改几个参数看效果 → 理解为什么要这样写 → 自己加新功能。

**不要试图第一次就理解所有代码**,先让它跑起来,信心就有了。

3️⃣ **把 @State 理解透,就学会了 ArkUI 的一半**

ArkUI 和传统 Android 开发最大的区别就是 **状态驱动**。搞懂 @State 怎么用、什么时候 UI 会刷新,你就能解释 ArkUI 的大部分行为了。


*本文首发于 CSDN,作者:一名正在学习鸿蒙开发的小白*  
*参考教程:[@AHuiHatedebug](https://blog.csdn.net/AHuiHatedebug) 的【鸿蒙原生应用开发--ArkUI--003】系列*

 官方文档:HarmonyOS 应用开发文档
- 开发者社区:华为开发者论坛
- 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net/

---

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐