ArkTS语法速成:15分钟掌握鸿蒙开发必备的TypeScript超集,附完整代码示例
本文是鸿蒙NEXT开发实战系列的第9篇,面向有JavaScript/TypeScript基础的前端工程师,介绍ArkTS的核心语法。ArkTS基于TypeScript扩展,新增了声明式UI、装饰器体系和并发模型等特性。文章详细对比了ArkTS与TypeScript的差异,重点讲解了装饰器体系(@Entry、@Component、@State等)、声明式UI语法(组件创建、属性设置、事件绑定)、状态
系列文章:鸿蒙NEXT开发实战系列 -- 第9篇(共9篇) 适合人群:有 JavaScript/TypeScript 基础,想快速上手鸿蒙开发的前端工程师 开发环境:DevEco Studio 5.0.5+ | HarmonyOS NEXT (API 14) 阅读时长:约15分钟
上一篇:ArkUI组件库完全指南 | 下一篇:无(系列末篇)
目录
引言: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 |
|---|---|---|
|
类型系统 |
渐进式类型,允许 |
严格静态类型, |
|
动态属性 |
支持动态添加对象属性 |
禁止动态属性,必须在类中显式声明 |
|
装饰器 |
实验性,需开启配置 |
一等公民,内置完整装饰器体系 |
|
声明式UI |
无原生支持(需React/Vue) |
原生支持, |
|
并发模型 |
单线程 + 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(); // 只在组件出现时执行一次
}
最佳实践总结:
-
状态设计:最小化
@State数量,只把驱动UI变化的数据声明为状态 -
组件拆分:单一职责,一个组件只做一件事
-
样式复用:用
@Styles和@Extend减少样式重复 -
列表性能:长列表使用
LazyForEach+cachedCount优化 -
资源释放:在
aboutToDisappear中清理定时器、Worker等资源
总结:ArkTS语法速查表
以下是 ArkTS 最常用语法的速查参考,建议收藏:
装饰器速查
|
装饰器 |
用途 |
示例 |
|---|---|---|
|
|
标记页面入口组件 |
|
|
|
标记自定义组件 |
|
|
|
组件内部状态 |
|
|
|
父到子单向传值 |
|
|
|
父子双向绑定 |
|
|
|
可复用UI片段 |
|
|
|
通用样式封装 |
|
|
|
扩展特定组件样式 |
|
|
|
监听状态变化 |
|
|
|
跨层级数据提供 |
|
|
|
跨层级数据消费 |
|
组件速查
|
组件 |
用途 |
基础用法 |
|---|---|---|
|
|
文本显示 |
|
|
|
图片显示 |
|
|
|
按钮 |
|
|
|
文本输入 |
|
|
|
垂直布局 |
|
|
|
水平布局 |
|
|
|
层叠布局 |
|
|
|
列表 |
|
|
|
滚动容器 |
|
|
|
循环渲染 |
|
生命周期速查
|
生命周期 |
触发时机 |
典型用途 |
|---|---|---|
|
|
组件创建后、build前 |
初始化数据、发起请求 |
|
|
组件销毁前 |
清理资源、取消订阅 |
|
|
页面显示时 |
刷新数据、恢复状态 |
|
|
页面隐藏时 |
暂停动画、保存状态 |
常用属性速查
// 尺寸
.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篇 |
环境搭建、项目结构、Hello World |
|
|
第2篇 |
基础组件、布局系统、自定义组件 |
|
|
第3篇 |
@State、@Prop、@Link、@Provide/Consume |
|
|
第4篇 |
Preferences、RDB、HTTP请求 |
|
|
第5篇 |
启动优化、内存管理、列表渲染 |
|
|
第9篇 |
ArkTS语法速成(本文) |
装饰器、声明式UI、状态管理、并发编程 |
作者简介:鸿蒙开发布道者,专注 HarmonyOS NEXT 开发技术分享。如有问题欢迎在评论区交流。
版权声明:本系列博客为原创文章,转载请注明出处。
标签:ArkTS | TypeScript | 鸿蒙开发 | 语法教程 | HarmonyOS | 声明式UI | 装饰器 | 状态管理 | 并发编程 | TaskPool | Worker
更多推荐



所有评论(0)