鸿蒙开发入门学习记录(二):ArkTS 语法与声明式 UI 上手

鸿蒙开发入门学习记录(二):ArkTS 语法与声明式 UI 上手

系列导航:
(一)环境搭建与第一个 Hello World ·
(二)ArkTS 语法与声明式 UI 上手 ·

前言

上一篇我们跑通了 Hello World,对声明式 UI 有了初步感受。本篇正式进入语言层面:先掌握 ArkTS 的基础语法,再搞懂 声明式 UI 的写法(组件、属性、事件、布局),最后用两个小例子练手。

本篇目标:看完后能独立写出"输入框 + 按钮 + 列表"这类基础交互页面。

一、ArkTS 是什么

ArkTS 是鸿蒙应用开发的主力语言,它基于 TypeScript 生态做了扩展,核心增加的能力包括:

  • 声明式 UI 描述:在代码里直接描述界面结构;
  • 自定义组件:把 UI 封装成可复用组件;
  • 状态管理:通过装饰器让 UI 随数据自动刷新;
  • 渲染控制:条件渲染(if/else)、循环渲染(ForEach)。

一句话:会 TS/JS 的同学上手很快,ArkTS 在类型上更严格、更偏静态约束(比如默认禁止 any、变量必须初始化等)。

二、ArkTS 基础语法速览

1. 变量与基本类型

// 显式声明类型
let age: number = 25;
let name: string = '小明';
let isDone: boolean = false;

// 类型推断:省略类型注解,编译器自动推断
let score = 98.5;          // number
const appName = '学习记录'; // string,const 常量不可重新赋值

// 数组
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ['Alice', 'Bob'];

// 对象(interface 定义结构)
interface User {
  id: number;
  name: string;
  age?: number;      // ? 表示可选属性
}

let user: User = { id: 1, name: '张三' };

// 联合类型
let value: number | string = 100;
value = '一百';       // 合法

// 枚举
enum Level { Low, Medium, High }
let lv: Level = Level.Medium;

注意:ArkTS 要求变量必须有确定类型(要么显式标注,要么能推断出来),不允许使用 any 逃逸类型检查。

2. 函数

// 普通函数
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数(鸿蒙代码里非常常见,事件回调基本都用它)
const multiply = (a: number, b: number): number => a * b;

// 默认参数与可选参数
function greet(name: string, title: string = '同学', suffix?: string): string {
  return `你好,${title} ${name}${suffix ?? ''}`;
}

console.log(greet('小明'));            // 你好,同学 小明
console.log(greet('小明', '老师'));     // 你好,老师 小明

3. 类与接口

ArkTS 完全支持面向对象:

interface Animal {
  name: string;
  speak(): void;
}

class Dog implements Animal {
  name: string;
  private age: number = 2;   // 访问修饰符:private / public / protected

  constructor(name: string) {
    this.name = name;
  }

  speak(): void {
    console.log(`${this.name} 在汪汪叫`);
  }

  getAge(): number {
    return this.age;
  }
}

const dog = new Dog('旺财');
dog.speak();   // 旺财 在汪汪叫

4. 空安全:? 和 !

ArkTS 强调空安全,对象或变量可能是空值时需要用 ? 保护,确认不为空时用 ! 断言:

let maybeName: string | undefined = undefined;

// 可选链:可能为空的调用用 ?.
console.log(maybeName?.length);   // 不报错,输出 undefined

// 空值合并:为空时给默认值
const len = maybeName?.length ?? 0;

// 非空断言:我确定它不为空(慎用,空时会崩溃)
// console.log(maybeName!.length);

三、声明式 UI 的核心写法

1. 一个页面的"母版"结构

@Entry
@Component
struct MyPage {
  // ① 状态变量(数据)
  @State count: number = 0;

  // ② 界面描述(build 里写 UI)
  build() {
    Column() {
      Text(`点击了 ${this.count} 次`)
    }
  }

  // ③ 方法(业务逻辑)
  increment() {
    this.count++;
  }
}

build() 的硬性规则(写多了会自然记住):

  • @Entry 组件的 build() 根节点必须且唯一,且必须是容器组件(Column、Row、Flex、Scroll 等);
  • 普通 @Component 组件根节点可以是非容器组件(比如直接是 Text);
  • ForEach 不能作为根节点。

2. 组件的三种用法

build() {
  Column({ space: 10 }) {          // ① 无参/有参创建
    // ② 有参:Text 传入字符串
    Text('我是文本')

    // ③ 属性配置(链式调用)
    Text('大字号红色文本')
      .fontSize(30)
      .fontColor('#FF0000')
      .fontWeight(FontWeight.Bold)

    // ④ 事件:onClick 点击事件
    Button('点我')
      .onClick(() => {
        console.info('被点击了');
      })

    // ⑤ 子组件:在容器里放多个子组件(上面的都是 Column 的子组件)
  }
}

3. 常用布局容器

容器排列方向典型场景
Column()纵向排列页面从上到下布局
Row()横向排列一行放多个元素
Flex()弹性布局更灵活的排列方式
Scroll()可滚动容器内容超出屏幕时
List() + ListItem()长列表聊天记录、商品列表
// Row + Column 组合,这是最常用的布局套路
build() {
  Column({ space: 20 }) {
    Row({ space: 10 }) {
      Text('左')
      Text('中')
      Text('右')
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)   // 主轴两端对齐

    Divider()   // 分割线
  }
  .width('100%')
  .padding(16)
}

4. 常用基础组件

build() {
  Column({ space: 16 }) {
    // 文本
    Text('Hello ArkUI')

    // 按钮:支持默认样式和自定义样式
    Button('默认按钮')
    Button('胶囊按钮', { type: ButtonType.Capsule })

    // 输入框
    TextInput({ placeholder: '请输入用户名' })
      .onChange((value: string) => {
        console.info(`输入了:${value}`);
      })

    // 图片(网络图或本地资源)
    Image($r('app.media.icon'))          // 本地资源图片
      .width(80)
      .height(80)
      .borderRadius(40)

    // 复选框
    Checkbox()
      .select(false)
      .onChange((checked: boolean) => {
        console.info(`选中状态:${checked}`);
      })
  }
}

5. 列表渲染:ForEach

展示动态数组,必须用 ForEach 循环渲染:

@Entry
@Component
struct ListPage {
  @State fruits: string[] = ['苹果', '香蕉', '橘子', '西瓜'];

  build() {
    Column() {
      // ① 数据源  ② 渲染函数(每个元素怎么显示)  ③ 键值生成器(尽量唯一,用于复用优化)
      ForEach(this.fruits, (item: string) => {
        Row() {
          Text(item)
            .fontSize(20)
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
      }, (item: string) => item)   // 键值生成器:用内容本身做 key
    }
    .width('100%')
    .padding(16)
  }
}

6. 条件渲染:if / else

@Entry
@Component
struct LoginPage {
  @State isLoggedIn: boolean = false;

  build() {
    Column() {
      if (this.isLoggedIn) {
        Text('欢迎回来!')
          .fontSize(24)
      } else {
        Button('点击登录')
          .onClick(() => {
            this.isLoggedIn = true;
          })
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

四、练手:计数器 + 登录表单

例子 1:计数器(状态 + 事件)

@Entry
@Component
struct CounterPage {
  @State count: number = 0;

  build() {
    Column({ space: 24 }) {
      Text(`${this.count}`)
        .fontSize(64)
        .fontWeight(FontWeight.Bold)

      Row({ space: 16 }) {
        Button('-')
          .onClick(() => {
            if (this.count > 0) {
              this.count--;
            }
          })
        Button('+')
          .onClick(() => {
            this.count++;
          })
      }

      if (this.count === 10) {
        Text('到达 10 啦,真棒!')
          .fontColor('#FF9500')
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

例子 2:登录表单(输入框 + 按钮 + 条件显示)

@Entry
@Component
struct LoginFormPage {
  @State username: string = '';
  @State password: string = '';
  @State tip: string = '';

  build() {
    Column({ space: 16 }) {
      Text('鸿蒙登录 Demo')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      TextInput({ placeholder: '请输入用户名', text: this.username })
        .onChange((value: string) => {
          this.username = value;
        })

      TextInput({ placeholder: '请输入密码', text: this.password })
        .type(InputType.Password)
        .onChange((value: string) => {
          this.password = value;
        })

      Button('登录')
        .width('100%')
        .onClick(() => {
          if (this.username === 'admin' && this.password === '123456') {
            this.tip = '登录成功!';
          } else {
            this.tip = '用户名或密码错误';
          }
        })

      if (this.tip !== '') {
        Text(this.tip)
          .fontColor(this.tip === '登录成功!' ? '#00B42A' : '#F53F3F')
      }
    }
    .width('100%')
    .padding(24)
  }
}

小经验:TextInput 用 text: this.username 把输入值"绑"回状态变量,再用 onChange 同步——这是声明式开发里"受控输入"的标准写法。

五、本篇踩坑记录

  1. 忘了 this:在 build() 里引用状态变量必须写 this.xxx,漏掉会报未定义;
  2. 字符串模板用反引号:${this.count} 必须写在反引号 ` 里,单双引号都不行;
  3. @State 变量要有初始值:ArkTS 不允许声明了不初始化;
  4. ForEach 的键值生成器要稳定唯一:用数组下标做 key 在增删时容易出问题,尽量用内容或 id;
  5. 属性要链式调用:.fontSize(30).fontColor('#333') 一个点接一个点写,不能写成赋值。

六、小结与下篇预告

本篇掌握了:

  • ✅ ArkTS 基础语法:变量、类型、函数、类、空安全
  • ✅ 声明式 UI 写法:build()、组件、属性、事件、子组件
  • ✅ 常用容器与组件:Column/Row/List、Text/Button/TextInput/Image
  • ✅ 渲染控制:if/else 条件渲染、ForEach 循环渲染
  • ✅ 两个小例子:计数器、登录表单

下一篇:进入鸿蒙开发的进阶核心——状态管理(@State/@Prop/@Link/@Provide/@Consume 等装饰器)和页面导航(Navigation),并综合实战一个完整的待办清单 App。

参考资料:

  • 华为官方:UI 开发(ArkTS 声明式开发范式)概述 — https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-ui-development-overview
  • 华为官方:声明式 UI 描述 — https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/arkts-declarative-ui-description
  • 华为官方:创建自定义组件 — https://device.harmonyos.com/cn/docs/apiref/harmonyos-guides/arkts-create-custom-components

如果对你有帮助,欢迎点赞收藏,下一篇见!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐