ArkTS 语言与状态管理详解指南
在HarmonyOS应用开发中,ArkTS是开发者必须掌握的核心语言,它到底是什么?简单来说,ArkTS是TypeScript的升级版本,专为声明式UI设计而打造。这意味着你在TypeScript中熟悉的大部分语法仍可沿用,但为了提升性能与开发效率,ArkTS做出了一些关键性的取舍。

复制
@Entry
@Component
struct Index {
build() {
Column() {
Text('Hello HarmonyOS')
}
}
}
关键语法规则:哪些被保留了,哪些被移除了?
先来看被“砍掉”的部分。为了提升代码健壮性与运行时效率,ArkTS对TypeScript实施了较为严格的安全限制。具体来说,不支持的内容包括:
any/unknown类型——告别“万能类型”,每个变量类型必须明确声明。as类型断言——请改用@Type装饰器来替代。- 枚举的运行时特性——仅支持数字枚举,更加轻量。
- JS动态执行相关:
eval()、new Function()、with语句,一概不可使用。 - 部分API受限:例如
Object.entries()/Object.keys()/Object.values()在某些场景下无法正常工作。
不必担心,有失必有得。ArkTS新增了一大波实用的语法糖和装饰器,这正是其核心价值所在。
- 组件与UI装饰器:
@Component、@Entry、@Builder、@Extend、@Styles,让UI结构复用变得直观便捷。 - 双向绑定语法:
$双美元符号,使父子组件之间、状态与UI之间的同步变得优雅顺畅。 - 状态管理家族:
@State、@Prop、@Link、@Provide/@Consume、@Watch,以及针对深层嵌套的@ObservedV2/@Trace。 - 参数与事件装饰器:
@Require(参数必填)、@Local(本地状态)、@Param(组件参数)、@Event(组件事件)。 - 响应式计算:
@Monitor(属性变化监听)、@Computed(计算属性)。
这一整套机制下来,ArkTS从语言层面为声明式开发铺平了道路。
状态管理深度解析:从基础到实战应用
状态管理是声明式UI的核心精髓。下面我们对最常用的几个装饰器逐一拆解分析。
@State —— 组件内部状态,一切变化的起点
可以理解为组件的“内政大臣”。它所修饰的变量一旦发生改变,整个组件就会重新渲染。基础用法非常简单:
复制
@Entry
@Component
struct CounterPage {
@State count: number = 0
@State message: string = '点击计数'
build() {
Column({ space: 20 }) {
Text(this.message).fontSize(24)
Text(`${this.count}`).fontSize(48).fontWeight(FontWeight.Bold)
Row({ space: 12 }) {
Button('-1').onClick(() => { this.count-- })
Button('+1').onClick(() => { this.count++ })
Button('重置').onClick(() => { this.count = 0 })
}
}
}
}
支持的数据类型:string / number / boolean / Object / Array / Map / Set,覆盖面较为全面。
一个重要提醒:@State 只能观测到对象或数组的第一层属性变化。如果数据是嵌套结构(例如对象中包含对象),则需要使用 @ObservedV2 和 @Trace 来补充,否则UI不会自动更新。
@Prop —— 从父组件到子组件,单向数据流
想象这样的场景:父组件需要将值传递给子组件,子组件可以读取并进行临时修改,但修改结果不会传回父组件。这就是 @Prop 的典型使用场景。
复制
@Component
struct ChildComponent {
@Prop title: string = ''
@Prop itemCount: number = 0
build() {
Text(`${this.title}: ${this.itemCount}件`)
.fontSize(16)
.onClick(() => { this.itemCount++ }) // 本地修改不影响父组件
}
}
@Entry
@Component
struct ParentComponent {
@State items: number = 5
build() {
Column() {
ChildComponent({ title: '商品', itemCount: this.items })
Text(`父组件值: ${this.items}`) // 仍然是5
}
}
}
注意:子组件虽然可以修改 @Prop 的值,但父组件一侧不会受影响。这保证了数据流的单向性与可预测性。
@Link —— 真正的双向绑定,子改父也变
如果父组件和子组件需要保持同步修改,@Link 就是最佳答案。使用时必须通过 $ 语法传递引用,子组件修改后,父组件自然跟着更新。
复制
@Component
struct SliderItem {
@Link value: number
build() {
Row() {
Slider({ value: this.value, min: 0, max: 100 })
.onChange((val) => { this.value = val })
Text(`${Math.round(this.value)}%`)
}
}
}
@Entry
@Component
struct MainPage {
@State progress: number = 30
build() {
Column() {
SliderItem({ value: $progress }) // $ 语法传递引用
Text(`当前进度: ${this.progress}%`)
}
}
}
@Provide / @Consume —— 跨层级共享数据,无需逐层传递
当数据需要在祖孙组件之间传递,且中间隔了很多层时,@Provide + @Consume 是最优雅的方案。祖辈提供数据,孙辈直接消费,中间组件完全无需插手。
复制
@Entry
@Component
struct GrandParent {
@Provide('theme') themeColor: string = '#007DFF'
@Provide('user') userInfo: UserInfo = { name: '张三', level: 'VIP' }
build() {
Column() {
Button('切换主题').onClick(() => {
this.themeColor = this.themeColor === '#007DFF' ? '#FF4400' : '#007DFF'
})
ParentComponent()
}
}
}
@Component
struct ParentComponent {
build() {
ChildComponent()
}
}
@Component
struct ChildComponent {
@Consume('theme') themeColor: string
@Consume('user') userInfo: UserInfo
build() {
Text(`主题色: ${this.themeColor}, 用户: ${this.userInfo.name}`)
.fontColor(this.themeColor)
}
}
@Watch —— 状态变化时执行额外操作
有时候状态变更后不仅要更新UI,还需要执行一些副作用逻辑,比如搜索、数据验证、记录日志。@Watch 正是为此而生。
复制
@Entry
@Component
struct SearchPage {
@State @Watch('onSearchChange') keyword: string = ''
@State resultList: string[] = []
onSearchChange(newValue: string) {
if (newValue.length > 0) {
this.doSearch(newValue)
} else {
this.resultList = []
}
}
build() {
Column() {
TextInput({ placeholder: '搜索', text: this.keyword })
.onChange((val) => { this.keyword = val })
List() {
ForEach(this.resultList, (item) => {
ListItem() { Text(item) }
})
}
}
}
}
@ObservedV2 / @Trace —— 处理深层嵌套状态(API Version 12+)
如前所述,@State 只能观察第一层,那么多层对象或数组该怎么办?@ObservedV2 和 @Trace 正是为此设计的救星。给类加上 @ObservedV2,给需要监听的属性加上 @Trace,深层次的修改就能被完美感知。
复制
@ObservedV2
class Order {
@Trace id: string = ''
@Trace items: OrderItem[] = []
@Trace status: string = 'pending'
}
@ObservedV2
class OrderItem {
@Trace name: string = ''
@Trace price: number = 0
@Trace quantity: number = 1
}
@Entry
@Component
struct OrderPage {
@State order: Order = new Order()
build() {
Column() {
// 修改嵌套属性会触发UI刷新
Button('修改数量').onClick(() => {
this.order.items[0].quantity += 1 // @Trace 确保观测到变化
})
}
}
}
@Builder —— 复用UI结构,高效轻巧
当多个地方需要显示同一种卡片或布局时,@Builder 几乎是必备技能。它就像自定义的UI“函数”,能彻底避免代码复制。
复制
@Entry
@Component
struct ProductList {
@State products: Product[] = []
@Builder ProductCard(product: Product) {
Column({ space: 8 }) {
Image(product.image).width(80).height(80)
Text(product.name).fontSize(14).fontWeight(FontWeight.Bold)
Text(`¥${product.price}`).fontSize(12).fontColor('#FF4400')
}
.padding(12)
.borderRadius(8)
.backgroundColor(Color.White)
}
@Builder EmptyState() {
Column() {
Image($r('app.media.empty')).width(120)
Text('暂无数据').fontSize(16).fontColor('#999999')
}
}
build() {
if (this.products.length > 0) {
List() {
ForEach(this.products, (item) => {
ListItem() { this.ProductCard(item) }
})
}
} else {
this.EmptyState()
}
}
}
@Styles —— 通用样式集合
如果多个组件需要共享一组完全相同的样式,@Styles 可以让你像搭积木一样组合样式,与CSS中的class概念非常相似。
复制
@Styles function commonButtonStyle() {
.width(200)
.height(40)
.borderRadius(20)
.fontSize(16)
.fontColor(Color.White)
}
@Styles function primaryBg() {
.backgroundColor('#007DFF')
}
@Styles function dangerBg() {
.backgroundColor('#FF4400')
}
@Entry
@Component
struct ButtonPage {
build() {
Column({ space: 12 }) {
Button('确认').commonButtonStyle().primaryBg()
Button('删除').commonButtonStyle().dangerBg()
}
}
}
@Extend —— 扩展原生组件样式
比 @Styles 更具针对性,@Extend 允许你针对特定组件(如 Button、Text)扩展专属样式。写起来非常自然。
复制
@Extend(Button)
function fancyButton(text: string, bgColor: ResourceColor) {
.type(ButtonType.Capsule)
.width(200)
.height(40)
.backgroundColor(bgColor)
.fontColor(Color.White)
.fontSize(16)
}
// 使用
Button('提交').fancyButton('提交', '#007DFF')
$ 双美元语法 —— 双向绑定利器
HarmonyOS中最直观的双向绑定写法。无论是文本输入、滑块拖动还是开关切换,一个 $ 符号即可实现双向绑定。
复制
@Entry
@Component
struct FormPage {
@State username: string = ''
@State age: number = 18
@State isAgree: boolean = false
build() {
Column() {
TextInput({ text: $username, placeholder: '姓名' })
Slider({ value: $age, min: 0, max: 100 })
Toggle({ type: ToggleType.Checkbox, isOn: $isAgree })
Text(`姓名: ${this.username}, 年龄: ${this.age}, 同意: ${this.isAgree}`)
}
}
}
条件渲染与循环渲染详解
这部分是构建动态UI的基础,ArkTS提供了非常直观的写法。
if/else 条件渲染
最常见的数据驱动UI场景——加载中、为空、有数据三种状态。
复制
build() {
Column() {
if (this.isLoading) {
LoadingProgress().color('#007DFF')
} else if (this.dataList.length === 0) {
this.EmptyState()
} else {
this.DataContent()
}
}
}
ForEach 循环渲染
常规列表渲染,每个Item需要唯一的key以提升性能。
复制
ForEach(
this.dataList,
(item: DataItem) => {
ListItem() { this.ItemBuilder(item) }
},
(item: DataItem) => item.id.toString() // keyGenerator,必须唯一
)
LazyForEach 懒加载渲染(大数据量场景)
当列表有成百上千条数据时,ForEach 可能出现卡顿。LazyForEach 通过懒加载只渲染可见区域,配合 IDataSource 接口,能够完美应对超长列表。
复制
// 数据源必须实现 IDataSource 接口
class MyDataSource implements IDataSource {
private dataList: DataItem[] = []
private listeners: DataChangeListener[] = []
totalCount(): number {
return this.dataList.length
}
getData(index: number): DataItem {
return this.dataList[index]
}
addData(item: DataItem) {
this.dataList.push(item)
this.listeners.forEach(l => l.onDataAdd(this.dataList.length - 1))
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.push(listener)
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const index = this.listeners.indexOf(listener)
if (index >= 0) this.listeners.splice(index, 1)
}
}
// 使用
LazyForEach(
this.dataSource,
(item: DataItem) => {
ListItem() { this.ItemBuilder(item) }
},
(item: DataItem) => item.id.toString()
)
