3、ArkTS语言基础:TypeScript快速回顾、ArkTS语法特性、声明式UI

好,咱们进入第三章。说实话,很多从Java或前端转过来的朋友,第一次看到ArkTS时都会有点懵——这到底是啥?是TypeScript吗?还是又一套新语言?

我直接告诉你答案:ArkTS是TypeScript的超集,但加了很多鸿蒙特有的约束和语法糖。说白了,你只要会TypeScript,上手ArkTS就是分分钟的事。但反过来,如果你只会JavaScript,那有些坑你可能得亲自踩一遍。

这一章,我带你把TypeScript的核心过一遍,然后重点讲ArkTS的语法特性和声明式UI。嗯,这部分是实战的基础,后面所有页面、组件、状态管理都离不开它。

3.1 TypeScript快速回顾:你只需要记住这些

我个人习惯,学任何语言先抓三个东西:类型系统、接口、泛型。TypeScript也一样,你把这三点搞明白,剩下的语法基本就是JavaScript那套。

3.1.1 类型注解

TypeScript最直观的变化就是变量后面可以加类型。我在项目中遇到过好几次,因为没写类型导致线上bug,排查起来特别痛苦。所以我的建议是:能写类型就写,别偷懒。

// 基础类型
let name: string = '鸿蒙';
let version: number = 3.0;
let isStable: boolean = true;

// 数组和元组
let apps: string[] = ['微信', '支付宝'];
let pair: [string, number] = ['API版本', 10];

// 联合类型
let id: string | number = 'abc123';
id = 456; // 没问题
我的小技巧: 如果你不确定类型,可以用 unknown 代替 anyany 会关闭类型检查,等于白写TypeScript。 unknown 至少还提醒你「这玩意儿类型不确定,用之前先判断一下」。

3.1.2 接口与类型别名

接口(Interface)是TypeScript的灵魂。你想想看,一个复杂的应用,如果没有接口定义,传参全靠猜,那得多痛苦。

interface AppConfig {
  name: string;
  version: number;
  permissions?: string[]; // 可选属性
  readonly bundleName: string; // 只读
}

// 类型别名
type DeviceInfo = {
  deviceType: 'phone' | 'tablet' | 'watch';
  screenWidth: number;
  screenHeight: number;
};

我个人更倾向于用 interface 定义对象结构,用 type 定义联合类型或工具类型。这不是硬性规定,但这样代码读起来更清晰。

3.1.3 泛型:写一次,用多处

泛型这东西,刚开始学的时候觉得没必要,但用久了就离不开。我记得有一次写一个数据缓存工具,如果没有泛型,我得为每种数据类型写一套逻辑。有了泛型,一个函数搞定。

// 泛型函数
function getCache<T>(key: string): T | null {
  const data = localStorage.getItem(key);
  return data ? JSON.parse(data) as T : null;
}

// 使用
const config = getCache<AppConfig>('appConfig');
重点: 在ArkTS中,泛型的用法和TypeScript完全一致。但要注意,ArkTS对某些高级泛型用法有限制,比如条件类型、映射类型等。如果你遇到编译报错,大概率是用了ArkTS不支持的特性。

3.2 ArkTS语法特性:TypeScript的「鸿蒙定制版」

ArkTS不是简单的TypeScript换皮。它做了很多取舍,目的是让代码更安全、性能更好。说白了,就是去掉了一些容易出问题的特性,加了一些鸿蒙特有的东西。

3.2.1 强制类型标注

在TypeScript里,你可以不写类型,让编译器自动推断。但在ArkTS里,所有变量、函数参数、返回值都必须显式标注类型。刚开始可能觉得麻烦,但习惯了你会发现,代码的可读性和可维护性提升了一大截。

// ❌ 错误:缺少类型标注
let count = 0;

// ✅ 正确:显式标注类型
let count: number = 0;

// 函数也必须标注
function add(a: number, b: number): number {
  return a + b;
}
注意: 这个规则对 letconst、函数参数、返回值都生效。我刚开始写ArkTS时,经常因为漏写类型被编译器「教育」。嗯,后来就长记性了。

3.2.2 禁止使用 anyundefined

这个限制可能让一些老手不太适应。在ArkTS里,你不能用 any 来逃避类型检查,也不能直接使用 undefined。如果你需要一个变量「可能有值也可能没有」,请用联合类型加 null

// ❌ 错误:不能使用 any
let data: any = fetchData();

// ✅ 正确:使用具体类型或 unknown
let data: string | null = fetchData();

// 可选属性用 ? 表示
interface User {
  name: string;
  age?: number; // 等价于 number | null
}

为什么会这样?因为 any 会绕过类型检查,容易引发运行时错误。 undefined 在JavaScript里也是个坑,很多bug都源于「忘记判断undefined」。ArkTS干脆一刀切,逼你写出更健壮的代码。

3.2.3 装饰器:给代码加点「魔法」

装饰器是ArkTS的一大亮点。它让你可以在不修改类本身的情况下,给类或方法添加额外功能。在鸿蒙开发中,装饰器主要用于组件注册、依赖注入等场景。

// @Entry 表示这是一个入口页面
// @Component 表示这是一个组件
@Entry
@Component
struct MyPage {
  @State message: string = 'Hello ArkTS';

  build() {
    Column() {
      Text(this.message)
        .fontSize(20)
    }
  }
}

你看, @Entry@Component 就是装饰器。它们告诉编译器:这个类是一个页面组件,需要特殊处理。我个人觉得,装饰器让代码的意图更明确,比写一堆配置要直观得多。

3.3 声明式UI:像搭积木一样构建界面

如果你用过React或Vue,那声明式UI对你来说应该不陌生。ArkTS的声明式UI和它们很像,但更简洁、更贴近原生。

3.3.1 核心思想:UI = 状态 + 模板

传统命令式UI是「我要做什么」,比如「创建一个按钮,设置它的文字,绑定点击事件」。声明式UI是「我想要什么」,比如「这里有一个按钮,文字是xxx,点击后做yyy」。你只需要描述最终状态,框架帮你搞定中间过程。

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

  build() {
    Column() {
      Text(`点击次数: ${this.count}`)
        .fontSize(24)
      Button('点我')
        .onClick(() => {
          this.count++;
        })
    }
    .padding(20)
  }
}

这段代码里, @State count 是状态, build() 里的 ColumnTextButton 是模板。当 count 变化时,UI会自动更新。你不用手动去操作DOM或原生控件,框架帮你做了。

核心概念: 在ArkTS中,所有UI组件都是函数调用。 Column() 创建一个列布局, Text() 创建文本, Button() 创建按钮。每个组件后面可以链式调用方法,比如 .fontSize().onClick(),用来设置属性和事件。

3.3.2 常用布局组件

鸿蒙提供了几个基础布局组件,我列个表给你看,方便你快速查阅。

组件 作用 常用属性
Column 垂直排列子组件 alignItemsjustifyContent
Row 水平排列子组件 alignItemsjustifyContent
Stack 层叠布局(类似绝对定位) alignContent
Flex 弹性布局 directionwrapjustifyContent
Grid 网格布局 columnsrowsgap

我在项目中用得最多的是 ColumnRow,配合 justifyContentalignItems 基本能搞定90%的布局需求。只有遇到复杂网格或自适应布局时,才会用到 GridFlex

3.3.3 状态管理:@State、@Prop、@Link

声明式UI的核心是状态管理。ArkTS提供了几个装饰器来管理状态,我挑三个最常用的讲。

  • @State:组件内部状态。状态变化时,组件重新渲染。
  • @Prop:从父组件传入的只读数据。父组件更新时,子组件自动更新。
  • @Link:父子组件共享状态。任何一方修改,双方都更新。
@Component
struct ChildComponent {
  @Prop title: string;
  @Link count: number;

  build() {
    Column() {
      Text(this.title)
      Button('增加')
        .onClick(() => {
          this.count++;
        })
    }
  }
}

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

  build() {
    Column() {
      ChildComponent({ title: '子组件', count: $count })
    }
  }
}

注意看,父组件用 $count@State 变量传给子组件的 @Link。这个 $ 符号表示「双向绑定」。嗯,这里容易出错,我刚开始写的时候经常忘记加 $,结果子组件改了值,父组件没反应。

避坑指南: 我曾经在项目里用 @Link 传递一个对象,结果子组件修改了对象的某个属性,父组件没有重新渲染。后来发现, @Link 只监听引用变化,不监听属性变化。解决方案是:要么重新赋值整个对象,要么用 @Observed 装饰器标记对象类型。

3.4 小结

这一章我们快速过了一遍TypeScript的核心语法,然后深入了ArkTS的语法特性和声明式UI。说白了,ArkTS就是TypeScript的「鸿蒙特供版」,去掉了容易出问题的特性,加上了装饰器和声明式UI的支持。

下一章,我们会用这些知识真正开始写一个跨设备应用。到时候你会发现,这些基础打得越扎实,后面写代码就越顺手。好,先消化一下,有问题随时翻回来看看。