系列文章:鸿蒙NEXT开发实战系列 -- 第9篇(共9篇) 适合人群:有 JavaScript/TypeScript 基础,想快速上手鸿蒙开发的前端工程师 开发环境:DevEco Studio 5.0.5+ | HarmonyOS NEXT (API 14) 阅读时长:约15分钟

上一篇:ArkUI组件库完全指南 | 下一篇:无(系列末篇)


目录

  1. 引言:ArkTS是什么?为什么选它?

  2. ArkTS vs TypeScript 核心差异对比表

  3. 装饰器体系详解

  4. 声明式UI语法

  5. 状态管理基础

  6. 并发编程

  7. 常见语法陷阱和最佳实践

  8. 总结:ArkTS语法速查表


引言:ArkTS是什么?为什么选它?

如果你是一名前端开发者,正在考虑进入鸿蒙生态,那么恭喜你——你的 TypeScript 经验可以直接复用

ArkTS 是鸿蒙NEXT(HarmonyOS NEXT)的官方应用开发语言。它基于 TypeScript 扩展,在保留 TS 语法主体的同时,加入了以下关键能力:

  • 声明式UI:类似 SwiftUI / Flutter 的描述方式,用代码直接"画"界面

  • 装饰器体系:通过 @Entry@Component@State 等装饰器驱动组件和状态

  • 静态类型强化:比标准 TypeScript 更严格的类型检查,减少运行时错误

  • 并发模型:内置 TaskPool 和 Worker,轻松实现多线程

一句话总结:ArkTS = TypeScript + 装饰器 + 声明式UI + 鸿蒙运行时。有 TS 基础的你,只需掌握"增量知识"即可上手。

本文的目标很明确:用最短的时间,帮你建立完整的 ArkTS 语法认知地图。每个知识点都配了可运行的代码示例,建议在 DevEco Studio 中边读边敲。


ArkTS vs TypeScript 核心差异对比表

在深入语法之前,先了解 ArkTS 相比标准 TypeScript 做了哪些"加法"和"减法":

对比维度

TypeScript

ArkTS

类型系统

渐进式类型,允许 any

严格静态类型,any 受限使用

动态属性

支持动态添加对象属性

禁止动态属性,必须在类中显式声明

装饰器

实验性,需开启配置

一等公民,内置完整装饰器体系

声明式UI

无原生支持(需React/Vue)

原生支持,build() 方法内描述UI

并发模型

单线程 + Web Worker

TaskPool + Worker 双线程模型

标准库

完整 ES 标准库

裁剪版,移除不安全的API

eval/with

可用(不推荐)

完全禁止

闭包限制

无限制

跨线程闭包有限制

ArkTS 限制的 TS 特性

// TypeScript 允许,ArkTS 禁止

// 1. any 类型受限
let data: any = 'hello';    // ArkTS 编译警告,应使用具体类型

// 2. 动态属性添加
let obj = { name: 'test' };
obj.age = 25;               // ArkTS 编译错误

// 3. eval 执行字符串代码
eval('console.log("hi")');  // ArkTS 完全禁止

// 4. structural typing 受限
// ArkTS 对接口匹配有更严格的规则

正确做法

// ArkTS 推荐写法

// 1. 使用具体类型
let data: string = 'hello';

// 2. 在类中显式声明属性
class Person {
  name: string = '';
  age: number = 0;
}
let p = new Person();
p.name = 'test';
p.age = 25;

装饰器体系详解

装饰器是 ArkTS 语法的核心。如果你用过 Angular 或 NestJS,会觉得非常熟悉。ArkTS 的装饰器分为以下几类:

3.1 @Entry -- 页面入口装饰器

@Entry 标记一个自定义组件为页面的入口组件。每个页面(ability)有且仅有一个 @Entry

// pages/Index.ets
@Entry
@Component
struct Index {
  @State message: string = 'Hello HarmonyOS NEXT';

  build() {
    Column() {
      Text(this.message)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3.2 @Component -- 自定义组件装饰器

@Component 标记一个 struct 为自定义组件,必须配合 build() 方法描述UI。

@Component
struct UserCard {
  @Prop userName: string;   // 从父组件接收
  @Prop userAge: number;

  build() {
    Row({ space: 12 }) {
      Image($r('app.media.avatar'))
        .width(60)
        .height(60)
        .borderRadius(30)

      Column({ space: 4 }) {
        Text(this.userName)
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        Text(`${this.userAge}岁`)
          .fontSize(14)
          .fontColor('#999')
      }
      .alignItems(HorizontalAlign.Start)
    }
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 4, color: '#1A000000' })
  }
}

3.3 @State -- 状态变量装饰器

@State 装饰的变量是组件内部的状态。当状态变化时,UI 自动重新渲染。

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

  build() {
    Column({ space: 16 }) {
      Text(`点击次数: ${this.count}`)
        .fontSize(24)

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

        Button('+')
          .onClick(() => {
            this.count++;
          })
      }
    }
  }
}

3.4 @Builder -- UI构建函数装饰器

@Builder 用于定义可复用的 UI 片段,类似于 React 的组件函数。

@Entry
@Component
struct BuilderExample {
  // 组件内 Builder
  @Builder
  TipCard(icon: Resource, text: string) {
    Row({ space: 8 }) {
      Image(icon)
        .width(20)
        .height(20)
      Text(text)
        .fontSize(14)
        .fontColor('#666')
    }
    .padding(12)
    .backgroundColor('#F5F5F5')
    .borderRadius(8)
  }

  build() {
    Column({ space: 12 }) {
      this.TipCard($r('app.media.info'), '这是一条提示信息')
      this.TipCard($r('app.media.success'), '操作成功')
    }
    .padding(16)
  }
}

// 全局 Builder(跨组件复用)
@Builder
function LoadingView() {
  Column() {
    LoadingProgress()
      .width(48)
      .height(48)
    Text('加载中...')
      .fontSize(14)
      .fontColor('#999')
      .margin({ top: 8 })
  }
  .justifyContent(FlexAlign.Center)
}

3.5 @Styles -- 样式复用装饰器

@Styles 用于封装通用样式,减少重复代码。

// 全局样式
@Styles
function cardStyle() {
  .padding(16)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .shadow({ radius: 8, color: '#1A000000', offsetY: 2 })
}

@Styles
function primaryTextStyle() {
  .fontSize(16)
  .fontWeight(FontWeight.Medium)
  .fontColor('#333333')
}

@Entry
@Component
struct StylesExample {
  build() {
    Column({ space: 12 }) {
      Text('卡片标题')
        .primaryTextStyle()    // 应用全局样式

      Text('卡片内容描述')
        .fontSize(14)
        .fontColor('#666666')
    }
    .cardStyle()               // 应用全局样式
    .margin(16)
  }
}

3.6 @Extend -- 扩展组件样式装饰器

@Extend 可以为特定组件扩展专属样式方法,比 @Styles 更有针对性。

// 只能扩展内置组件
@Extend(Text)
function titleText() {
  .fontSize(20)
  .fontWeight(FontWeight.Bold)
  .fontColor('#1A1A1A')
}

@Extend(Text)
function subtitleText() {
  .fontSize(14)
  .fontColor('#999999')
  .margin({ top: 4 })
}

@Extend(TextInput)
function searchInput() {
  .backgroundColor('#F5F5F5')
  .borderRadius(20)
  .height(40)
  .padding({ left: 16, right: 16 })
  .placeholderColor('#CCCCCC')
}

@Entry
@Component
struct ExtendExample {
  build() {
    Column({ space: 16 }) {
      Text('页面标题')
        .titleText()

      Text('副标题说明文字')
        .subtitleText()

      TextInput({ placeholder: '搜索...' })
        .searchInput()
    }
    .padding(16)
    .width('100%')
  }
}

声明式UI语法

声明式UI是 ArkTS 最直观的特征。掌握以下四种模式,就能写出大部分界面。

4.1 组件创建语法

ArkTS 使用 构造函数调用 的方式创建组件:

// 语法格式:组件名({ 参数 }).属性1().属性2()

// 基础组件创建
Text('Hello')                              // 无参
Button('点击我')                            // 带参数
Image($r('app.media.logo'))                // 带资源参数
List({ space: 8, scroller: this.scroller }) // 带配置对象

// 带子组件的容器
Column() {
  Text('子组件1')
  Text('子组件2')
}

4.2 属性设置(链式调用)

ArkTS 通过链式调用设置属性,代码可读性极强:

Text('鸿蒙开发')
  .fontSize(24)                    // 字体大小
  .fontWeight(FontWeight.Bold)     // 字体粗细
  .fontColor('#FF6B35')            // 字体颜色
  .textAlign(TextAlign.Center)     // 文本对齐
  .maxLines(2)                     // 最大行数
  .textOverflow({ overflow: TextOverflow.Ellipsis })  // 溢出处理
  .width('100%')                   // 宽度
  .height(60)                      // 高度
  .margin({ top: 16, bottom: 8 })  // 外边距
  .padding({ left: 12, right: 12 }) // 内边距
  .backgroundColor('#FFFFFF')      // 背景色
  .borderRadius(8)                 // 圆角

4.3 事件绑定

通过 .onClick().onTouch() 等方法绑定事件:

@Component
struct EventExample {
  @State inputText: string = '';
  @State tapCount: number = 0;

  build() {
    Column({ space: 16 }) {
      // 点击事件
      Button(`点击了 ${this.tapCount} 次`)
        .onClick(() => {
          this.tapCount++;
          console.info('按钮被点击');
        })

      // 输入事件
      TextInput({ text: this.inputText })
        .onChange((value: string) => {
          this.inputText = value;
        })
        .onSubmit(() => {
          console.info('提交内容: ' + this.inputText);
        })

      // 触摸事件
      Text('触摸我试试')
        .fontSize(18)
        .onTouch((event: TouchEvent) => {
          if (event.type === TouchType.Down) {
            console.info('手指按下');
          } else if (event.type === TouchType.Up) {
            console.info('手指抬起');
          }
        })

      // 手势事件
      Text('双击放大')
        .fontSize(18)
        .gesture(
          TapGesture({ count: 2 })
            .onAction(() => {
              console.info('双击触发');
            })
        )
    }
    .padding(16)
  }
}

4.4 条件渲染与循环渲染

条件渲染 -- 使用三元表达式或 if/else

@Component
struct ConditionalExample {
  @State isLoggedIn: boolean = false;
  @State isLoading: boolean = false;

  build() {
    Column({ space: 16 }) {
      // 三元表达式
      Text(this.isLoggedIn ? '欢迎回来' : '请登录')
        .fontSize(20)

      // if 条件渲染
      if (this.isLoading) {
        LoadingProgress()
          .width(40)
          .height(40)
      }

      // if-else 条件渲染
      if (this.isLoggedIn) {
        Button('退出登录')
          .onClick(() => { this.isLoggedIn = false; })
      } else {
        Button('登录')
          .onClick(() => { this.isLoggedIn = true; })
      }
    }
  }
}

循环渲染 -- 使用 ForEach

@Component
struct ListExample {
  private fruits: string[] = ['苹果', '香蕉', '橙子', '葡萄'];

  build() {
    Column({ space: 8 }) {
      ForEach(this.fruits, (item: string, index: number) => {
        Row({ space: 12 }) {
          Text(`${index + 1}.`)
            .fontSize(16)
            .fontColor('#FF6B35')
          Text(item)
            .fontSize(16)
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FAFAFA')
        .borderRadius(8)
      }, (item: string) => item)  // keyGenerator,用于性能优化
    }
    .padding(16)
  }
}

状态管理基础

状态管理是 ArkTS 的灵魂。理解 @State@Prop@Link 三者的关系,就能应对大多数场景。

5.1 @State基本用法

@State 装饰的变量是组件的"私有状态"。当变量值变化时,框架会自动触发UI刷新。

@Entry
@Component
struct StateDemo {
  @State title: string = 'ArkTS速成';
  @State count: number = 0;
  @State isActive: boolean = false;

  build() {
    Column({ space: 20 }) {
      Text(this.title)
        .fontSize(28)
        .fontWeight(FontWeight.Bold)

      Text(`计数: ${this.count}`)
        .fontSize(20)

      Button(this.isActive ? '已激活' : '未激活')
        .backgroundColor(this.isActive ? '#007DFF' : '#CCCCCC')
        .onClick(() => {
          this.isActive = !this.isActive;
          this.count++;
        })
    }
    .padding(24)
    .width('100%')
  }
}

5.2 @Prop父子传值

@Prop 实现父组件向子组件的单向数据传递。子组件可以修改 @Prop 变量,但不会影响父组件。

// 子组件
@Component
struct ChildComponent {
  @Prop message: string;          // 从父组件接收
  @Prop localClicks: number = 0;  // 支持默认值

  build() {
    Column({ space: 8 }) {
      Text(this.message)
        .fontSize(18)
      Button(`子组件点击: ${this.localClicks}`)
        .onClick(() => {
          this.localClicks++;     // 修改只影响子组件自身
        })
    }
    .padding(16)
    .backgroundColor('#E3F2FD')
    .borderRadius(8)
  }
}

// 父组件
@Entry
@Component
struct ParentComponent {
  @State parentMessage: string = '来自父组件的消息';

  build() {
    Column({ space: 20 }) {
      Text('父组件区域')
        .fontSize(24)

      ChildComponent({
        message: this.parentMessage,
        localClicks: 0
      })
    }
    .padding(16)
  }
}

5.3 @Link双向绑定

@Link 实现父子组件之间的双向数据绑定。子组件修改 @Link 变量会同步到父组件。

// 子组件:使用 @Link 双向绑定
@Component
struct SliderControl {
  @Link value: number;   // 注意:@Link 不能设置默认值

  build() {
    Column({ space: 8 }) {
      Text(`当前值: ${this.value.toFixed(0)}`)
        .fontSize(18)

      Slider({
        value: this.value,
        min: 0,
        max: 100,
        step: 1
      })
        .onChange((val: number) => {
          this.value = val;  // 修改会同步到父组件
        })
    }
    .padding(12)
    .backgroundColor('#FFF3E0')
    .borderRadius(8)
  }
}

// 父组件
@Entry
@Component
struct LinkDemo {
  @State sliderValue: number = 50;

  build() {
    Column({ space: 24 }) {
      Text(`父组件显示: ${this.sliderValue.toFixed(0)}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      SliderControl({ value: $sliderValue })  // 使用 $ 传递引用

      // 进度条同步展示
      Progress({ value: this.sliderValue, total: 100, type: ProgressType.Linear })
        .width('100%')
    }
    .padding(24)
  }
}

核心区别记忆

  • @State:组件私有状态,自己用

  • @Prop:父传子,单向(父 -> 子),子改不影响父

  • @Link:父子双向绑定,子改父也变


并发编程

鸿蒙NEXT提供了两种多线程方案,适用于不同场景。

6.1 TaskPool使用

TaskPool 适合短时、频繁的异步任务。系统自动管理线程生命周期,开发者无需手动创建和销毁线程。

import { taskpool } from '@kit.CoreKit';

// Step 1: 定义任务函数(必须是全局函数或静态方法)
@Concurrent
function heavyComputation(data: number[]): number {
  let result = 0;
  for (let i = 0; i < data.length; i++) {
    result += Math.sqrt(data[i]) * Math.sin(data[i]);
  }
  return result;
}

// Step 2: 提交任务到 TaskPool
async function runTask() {
  // 准备数据
  const data: number[] = [];
  for (let i = 0; i < 1000000; i++) {
    data.push(i);
  }

  // 创建任务
  const task = new taskpool.Task(heavyComputation, data);

  try {
    // 执行任务并获取结果
    const result: number = await taskpool.execute(task) as number;
    console.info('计算结果: ' + result);
  } catch (err) {
    console.error('任务执行失败: ' + JSON.stringify(err));
  }
}

// Step 3: 在组件中调用
@Entry
@Component
struct TaskPoolDemo {
  @State result: string = '等待计算...';

  build() {
    Column({ space: 16 }) {
      Text(this.result)
        .fontSize(18)

      Button('开始计算')
        .onClick(async () => {
          this.result = '计算中...';
          await runTask();
          this.result = '计算完成';
        })
    }
    .padding(24)
  }
}

6.2 Worker线程

Worker 适合长时间运行的后台任务。需要手动管理线程的创建和销毁。

Step 1 -- 创建 Worker 脚本 workers/ComputeWorker.ets

// workers/ComputeWorker.ets
import { worker, ThreadWorkerGlobalScope } from '@kit.CoreKit';

const workerPort: ThreadWorkerGlobalScope = self as ThreadWorkerGlobalScope;

// 监听主线程消息
workerPort.onmessage = (message: MessageEvent) => {
  const data = message.data as number[];

  // 执行耗时计算
  let result = 0;
  for (let i = 0; i < data.length; i++) {
    result += data[i] * data[i];
  }

  // 将结果回传给主线程
  workerPort.postMessage({ result: result });
};

Step 2 -- 在主线程中创建和使用 Worker:

import { worker } from '@kit.CoreKit';

@Entry
@Component
struct WorkerDemo {
  @State result: string = '等待中...';

  private computeWorker: worker.ThreadWorker | null = null;

  // 创建 Worker
  aboutToAppear() {
    this.computeWorker = new worker.ThreadWorker('workers/ComputeWorker.ets');

    // 监听 Worker 回传的消息
    this.computeWorker.onmessage = (message: MessageEvent) => {
      const data = message.data as { result: number };
      this.result = `计算结果: ${data.result}`;
    };

    this.computeWorker.onerror = (error: ErrorEvent) => {
      this.result = `错误: ${error.message}`;
    };
  }

  // 销毁 Worker
  aboutToDisappear() {
    if (this.computeWorker) {
      this.computeWorker.terminate();
      this.computeWorker = null;
    }
  }

  build() {
    Column({ space: 16 }) {
      Text(this.result)
        .fontSize(18)

      Button('开始后台计算')
        .onClick(() => {
          if (this.computeWorker) {
            this.result = '计算中...';
            const data = Array.from({ length: 100000 }, (_, i) => i);
            this.computeWorker.postMessage(data);  // 发送数据给 Worker
          }
        })
    }
    .padding(24)
  }
}

TaskPool vs Worker 选型指南

场景

推荐方案

原因

短时计算(< 3秒)

TaskPool

自动管理线程,开发简单

长时间运行(> 3秒)

Worker

独立线程,不影响主线程

频繁提交小任务

TaskPool

线程池复用,性能更优

需要持续通信

Worker

支持持续的双向消息传递


常见语法陷阱和最佳实践

陷阱一:@State 变量的引用类型

// 错误:直接修改对象属性不会触发UI刷新
@Component
struct BadExample {
  @State person: { name: string, age: number } = { name: 'Tom', age: 20 };

  build() {
    Column() {
      Text(`${this.person.name}, ${this.person.age}岁`)
      Button('长大一岁').onClick(() => {
        this.person.age++;  // 不会触发UI刷新!
      })
    }
  }
}

// 正确:重新赋值对象以触发刷新
@Component
struct GoodExample {
  @State person: { name: string, age: number } = { name: 'Tom', age: 20 };

  build() {
    Column() {
      Text(`${this.person.name}, ${this.person.age}岁`)
      Button('长大一岁').onClick(() => {
        this.person = { ...this.person, age: this.person.age + 1 };  // 触发刷新
      })
    }
  }
}

陷阱二:ForEach 必须提供 keyGenerator

// 错误:缺少 keyGenerator,可能导致列表渲染异常
ForEach(this.list, (item: string) => {
  Text(item)
})

// 正确:提供唯一的 key
ForEach(this.list, (item: string) => {
  Text(item)
}, (item: string) => item)  // 使用数据本身作为 key

陷阱三:build() 方法中避免副作用

// 错误:build 中执行网络请求
build() {
  Column() {
    Text(this.loadData());  // 每次刷新都发起请求!
  }
}

// 正确:在生命周期中执行
aboutToAppear() {
  this.loadData();  // 只在组件出现时执行一次
}

最佳实践总结

  1. 状态设计:最小化 @State 数量,只把驱动UI变化的数据声明为状态

  2. 组件拆分:单一职责,一个组件只做一件事

  3. 样式复用:用 @Styles@Extend 减少样式重复

  4. 列表性能:长列表使用 LazyForEach + cachedCount 优化

  5. 资源释放:在 aboutToDisappear 中清理定时器、Worker等资源


总结:ArkTS语法速查表

以下是 ArkTS 最常用语法的速查参考,建议收藏:

装饰器速查

装饰器

用途

示例

@Entry

标记页面入口组件

@Entry @Component struct Index {}

@Component

标记自定义组件

@Component struct MyComp {}

@State

组件内部状态

@State count: number = 0

@Prop

父到子单向传值

@Prop title: string

@Link

父子双向绑定

@Link value: number

@Builder

可复用UI片段

@Builder MyBuilder() { ... }

@Styles

通用样式封装

@Styles function myStyle() { ... }

@Extend

扩展特定组件样式

@Extend(Text) function title() { ... }

@Watch

监听状态变化

@State @Watch('onChange') count: number = 0

@Provide

跨层级数据提供

@Provide('key') data: string = ''

@Consume

跨层级数据消费

@Consume('key') data: string

组件速查

组件

用途

基础用法

Text

文本显示

Text('内容').fontSize(16)

Image

图片显示

Image($r('app.media.img'))

Button

按钮

Button('点击').onClick(() => {})

TextInput

文本输入

TextInput({ placeholder: '提示' })

Column

垂直布局

Column({ space: 8 }) { ... }

Row

水平布局

Row({ space: 8 }) { ... }

Stack

层叠布局

Stack() { ... }

List

列表

List({ space: 8 }) { ... }

Scroll

滚动容器

Scroll() { ... }

ForEach

循环渲染

ForEach(arr, (item) => { ... }, key)

生命周期速查

生命周期

触发时机

典型用途

aboutToAppear()

组件创建后、build前

初始化数据、发起请求

aboutToDisappear()

组件销毁前

清理资源、取消订阅

onPageShow()

页面显示时

刷新数据、恢复状态

onPageHide()

页面隐藏时

暂停动画、保存状态

常用属性速查

// 尺寸
.width('100%').height(50)
.size({ width: 100, height: 100 })

// 间距
.padding(16).margin({ top: 8, left: 12 })
.padding({ top: 10, bottom: 10, left: 16, right: 16 })

// 背景与边框
.backgroundColor('#FFFFFF')
.borderRadius(8)
.border({ width: 1, color: '#EEEEEE' })

// 文本
.fontSize(16).fontWeight(FontWeight.Bold)
.fontColor('#333333').textAlign(TextAlign.Center)

// 透明度与可见性
.opacity(0.8)
.visibility(Visibility.Hidden)

// 手势
.onClick(() => {})
.onTouch((event: TouchEvent) => {})
.gesture(TapGesture({ count: 2 }).onAction(() => {}))

学习建议:语法只是工具,真正的掌握来自实践。建议在 DevEco Studio 中创建一个新项目,把本文的每个代码示例都跑一遍。遇到问题时,善用官方 API 文档和社区论坛。


系列文章导航

篇章

标题

核心内容

第1篇

鸿蒙NEXT开发从零到一

环境搭建、项目结构、Hello World

第2篇

ArkUI组件库完全指南

基础组件、布局系统、自定义组件

第3篇

状态管理一文通

@State、@Prop、@Link、@Provide/Consume

第4篇

数据持久化与网络请求全攻略

Preferences、RDB、HTTP请求

第5篇

性能优化实战指南

启动优化、内存管理、列表渲染

第9篇

ArkTS语法速成(本文)

装饰器、声明式UI、状态管理、并发编程


作者简介:鸿蒙开发布道者,专注 HarmonyOS NEXT 开发技术分享。如有问题欢迎在评论区交流。

版权声明:本系列博客为原创文章,转载请注明出处。


标签:ArkTS | TypeScript | 鸿蒙开发 | 语法教程 | HarmonyOS | 声明式UI | 装饰器 | 状态管理 | 并发编程 | TaskPool | Worker

Logo

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

更多推荐