前言

很多人第一次看 HarmonyOS 的代码,发现它长得挺像 TypeScript,又好像多了些奇怪的东西——@Component@Statebuild() 方法……这些是啥?

ArkTS 是华为基于 TypeScript 4.x 扩展的编程语言。TypeScript 能写的,ArkTS 基本都能写;ArkTS 还额外提供了一套用于构建 UI 的扩展语法。本篇我们从项目源码出发,把 ArkTS 的基础语法捋一遍,让你读懂后续所有代码。

项目预览

一、ArkTS 的核心基础

1.1 变量声明

ArkTS 使用 let(变量)和 const(常量),这和 TypeScript / JavaScript 完全一样:

let count: number = 0;         // 数字类型
const name: string = 'Hello';  // 字符串常量
let isVisible: boolean = true; // 布尔类型
let items: string[] = [];      // 字符串数组

提示:ArkTS 要求强制类型标注,不能像 JS 那样写 let x = 1 就不管类型了。变量声明时最好明确写出类型,养成好习惯。

1.2 函数定义

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

// 箭头函数
const greet = (name: string): string => {
  return `Hello, ${name}`;
};

// async 异步函数(项目中大量使用)
async function fetchData(): Promise<string> {
  let result = await someAsyncOperation();
  return result;
}

项目中的 MapUtil.ets 里大量使用了 async/await,比如获取当前位置:

// entry/src/main/ets/utils/MapUtil.ets
async getMyLocation(): Promise<geoLocationManager.Location> {
  let location: geoLocationManager.Location = await geoLocationManager.getCurrentLocation();
  return location;
}

async 表示这个方法是异步的,await 等待异步操作完成,返回类型是 Promise<Location>

二、interface 和 class

2.1 interface 接口

接口定义数据的"形状",规定一个对象应该有哪些字段:

// entry/src/main/ets/model/StationData.ets
export interface StationData {
  image: ResourceStr;   // 图片资源(可以是字符串或资源引用)
  id: string;           // 唯一标识
  name: string;         // 加油站名称
  addr: string;         // 地址
  latitude: number;     // 纬度
  longitude: number;    // 经度
}

这是项目里的加油站数据模型。定义 interface 之后,我们创建的每个加油站数据对象都必须包含这些字段,少了会报错

// 正确的使用方式
let station: StationData = {
  image: $r('app.media.image1'),
  id: '1',
  name: '中国石化加油站(AA站)',
  addr: 'N市J区XX大街587号',
  latitude: 31.937176963332842,
  longitude: 118.86018812656404,
};

2.2 class 类

类是对功能的封装。项目中的工具类都用 class 实现:

// entry/src/main/ets/utils/Logger.ets
export class Logger {
  private static domain: number = 0x0000;    // 私有静态属性
  private static prefix: string = 'NearByGasStationDemo';

  static info(...args: string[]): void {     // 静态方法,直接 Logger.info() 调用
    hilog.info(Logger.domain, Logger.prefix, Logger.format, args);
  }

  static error(...args: string[]): void {
    hilog.error(Logger.domain, Logger.prefix, Logger.format, args);
  }
}

static 的意思:不需要实例化就能调用,直接 Logger.info('消息') 用。

private 的意思:只在类内部能访问,外部访问不到。

// 外部使用方式
Logger.info('testTag', '初始化成功');
Logger.error('testTag', `出错了: ${err.message}`);

2.3 @Observed 装饰的类

这是 ArkTS 特有的扩展。给一个类加上 @Observed,表示这个类的实例被 UI 框架"观察",它的属性变化会触发 UI 刷新:

// entry/src/main/ets/utils/MapUtil.ets
@Observed
export class MapUtil {
  // ... 方法实现
}

提示:@Observed 常和 @ObjectLink 装饰器配合使用,实现深层数据的响应式更新。本项目中 MapUtil 使用 @Observed 主要是为了标识这是一个可观察对象,为后续状态联动做准备。

三、export 和 import

ArkTS 使用 ES Module 的模块系统,跟 TypeScript/JavaScript 完全一样。

3.1 导出

// 导出 interface
export interface StationData { ... }

// 导出常量
export const STATION_LIST: StationData[] = [ ... ];

// 导出 class
export class Logger { ... }

// 默认导出(一个文件只能有一个)
export default class EntryAbility extends UIAbility { ... }

3.2 导入

// 从相对路径导入
import { Constants } from '../common/Constants';
import { Logger } from '../utils/Logger';
import { STATION_LIST, StationData } from '../model/StationData';

// 从 Kit 包导入(HarmonyOS 系统能力)
import { map, mapCommon, MapComponent } from '@kit.MapKit';
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';

@kit.MapKit 这样的 @kit.xxx 是 HarmonyOS 提供的系统 Kit 包,不需要手动安装,直接 import 就能用。

四、ArkTS 特有扩展:装饰器

这是 ArkTS 和普通 TypeScript 差别最大的地方。装饰器以 @ 开头,用来给类、方法、属性加上"标签",告诉框架这个东西是干什么用的。

4.1 组件相关装饰器

装饰器 作用
@Entry 标记一个组件为页面入口(每个页面有且只有一个)
@Component 标记为 ArkUI 组件(可复用的 UI 单元)
@Builder 标记为构建函数(可以在 build() 里调用的 UI 片段)
// MainPage.ets
@Entry                    // 这是页面入口
@Component               // 这是一个组件
struct MainPage {
  build() {
    // UI 构建逻辑
  }
}

提示:注意是 struct(结构体)而不是 class。在 ArkTS 中,UI 组件必须用 struct 定义,不能用 class

4.2 状态管理装饰器

装饰器 作用
@State 组件内部状态,变化时触发 UI 刷新
@StorageProp 从 AppStorage 读取值,AppStorage 变化时同步更新
@Prop 从父组件接收数据(单向同步)
@Link 从父组件接收数据(双向同步)

项目中最常见的用法:

// GasStationPage.ets
@Component
struct GasStationPage {
  @State stationInfoList: StationData[] = [];    // 内部状态:加油站列表
  @State latitude: number = 0;                    // 内部状态:当前纬度
  @State isShow: boolean = false;                 // 内部状态:是否显示底部弹窗

  @StorageProp('bottomRectHeight')               // 从全局存储读取导航栏高度
  bottomRectHeight: number = 0;

  @StorageProp('topRectHeight')                  // 从全局存储读取状态栏高度
  topRectHeight: number = 0;
}

@State 是最重要的:当 @State 修饰的变量值发生变化时,组件会自动重新渲染,不需要手动调用任何刷新方法。这是响应式编程的核心。

五、可选类型与非空断言

5.1 可选类型(?

在 ArkTS 中,? 表示这个属性或参数是可选的:

// 可选属性
private mapOptions?: mapCommon.MapOptions;  // 可能是 undefined

// 可选参数
moveToGasStation(latitude?: number, longitude?: number): void {
  if (latitude && longitude) {  // 需要先检查是否有值
    // ...
  }
}

5.2 非空断言(!

当你确定某个值不是 null/undefined,但 TypeScript 不知道,可以用 ! 告诉编译器"相信我,它不为空":

// MapUtil.ets
mapUtil.moveToCurrentPosition(
  this.latitude,
  this.longitude,
  this.mapController as map.MapComponentController  // 类型断言
);

as 是类型断言,相当于"我确定它是这个类型"。

六、展开运算符与数组操作

项目中用到了一些 TypeScript 常见的数组操作:

// forEach 遍历
this.stationInfoList.forEach(async (stationItem: StationData) => {
  await mapUtil.addMapMaker(
    stationItem.latitude,
    stationItem.longitude,
    this.mapController as map.MapComponentController
  );
});

forEach 接收一个回调函数,对数组每个元素执行操作。注意这里的回调是 async 的,意味着每次添加标记都是异步操作。

总结

这篇文章覆盖了项目中用到的所有 ArkTS 基础语法:

  • 变量与函数:和 TypeScript 基本一致
  • interface:定义数据模型的形状
  • class:封装工具方法,static 方法直接调用
  • export/import:模块化管理代码
  • 装饰器:ArkTS 特有,@Entry@Component@State 是最常用的三个
  • 可选类型与非空断言:处理可能为空的情况

下一篇我们开始讲 ArkUI 声明式 UI,搞清楚 ColumnRowTextImage 这些组件是怎么拼在一起构建界面的。

Logo

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

更多推荐