3、ArkTS语言基础:TypeScript快速回顾、ArkTS声明式语法、状态管理机制
好,咱们进入第三章。说实话,ArkTS 是鸿蒙开发绕不开的第一道坎。很多从 Java 或前端转过来的同学,第一眼看到 ArkTS 会觉得「这不就是 TypeScript 吗?」—— 嗯,对,也不全对。
ArkTS 基于 TypeScript,但做了不少裁剪和增强。我刚开始接触时也踩过坑,比如装饰器用错了、状态没刷新。今天咱们就把这三个核心点捋清楚:TypeScript 快速回顾、ArkTS 声明式语法、状态管理机制。
3.1 TypeScript 快速回顾
ArkTS 的语法核心就是 TypeScript。如果你写过 TS,上手会很快。但 ArkTS 做了减法 —— 它去掉了 any 类型、去掉了 enum,也去掉了部分高级类型体操。说白了,ArkTS 更强调「安全」和「可预测」。
3.1.1 类型注解
我个人习惯,写 ArkTS 时第一件事就是给变量加上类型。别偷懒,类型就是文档。
// 基础类型
let name: string = '鸿蒙'
let version: number = 3.0
let isStable: boolean = true
// 数组和联合类型
let devices: string[] = ['手机', '平板', '手表']
let status: 'online' | 'offline' = 'online'
// 对象类型
interface DeviceInfo {
id: number
name: string
battery: number
}
let myDevice: DeviceInfo = { id: 1, name: 'MatePad', battery: 85 }
any —— 在 ArkTS 里你也没法用。
3.1.2 接口与泛型
接口在 ArkTS 里用得非常多。尤其是定义组件 Props 的时候。泛型呢?我建议你掌握基础用法就行,不用钻太深。
// 泛型函数
function getFirst<T>(arr: T[]): T | undefined {
return arr[0]
}
// 泛型接口
interface ResponseData<T> {
code: number
data: T
message: string
}
// 实际使用
let userResponse: ResponseData<{ name: string; age: number }> = {
code: 200,
data: { name: '张三', age: 28 },
message: '成功'
}
any 和 unknown。如果你需要灵活类型,用泛型约束替代。
3.1.3 装饰器
装饰器是 ArkTS 的「灵魂」。TypeScript 里装饰器还是实验特性,但在 ArkTS 里它是核心语法。你写组件、管理状态、定义样式,都离不开它。
// 一个简单的装饰器示例
function log(target: any, key: string) {
console.log(`调用方法: ${key}`)
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b
}
}
不过在实际的 ArkTS 开发中,你更多会用到框架内置的装饰器,比如 @Component、@State、@Prop 这些。咱们下面细说。
3.2 ArkTS 声明式语法
ArkTS 的 UI 写法是声明式的。什么意思?你描述「界面长什么样」,框架负责「怎么画出来」。这和命令式(比如传统 Java UI)完全不同。
我记得第一次用声明式写 UI 时,最大的感受是:代码量少了一半。你想想看,不用再写 findViewById、不用手动 setText,多清爽。
3.2.1 @Component 和 @Entry
每个页面都是一个组件。用 @Component 装饰一个 struct,用 @Entry 标记入口。
@Entry
@Component
struct HelloPage {
build() {
Column() {
Text('你好,鸿蒙')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('点击我')
.onClick(() => {
console.log('按钮被点击了')
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
build() 方法里写条件判断,结果界面死活不刷新。后来发现 build() 里不能写 if 以外的逻辑语句。条件渲染要用 if/else 表达式,不能写 for 循环 —— 循环用 ForEach。
3.2.2 @Builder 自定义构建函数
当 UI 结构重复时,别复制粘贴。用 @Builder 抽出来。
@Entry
@Component
struct CardList {
@Builder
CardItem(title: string, desc: string) {
Column() {
Text(title).fontSize(18).fontWeight(FontWeight.Medium)
Text(desc).fontSize(14).fontColor('#666')
}
.padding(16)
.backgroundColor('#f5f5f5')
.borderRadius(8)
.margin({ bottom: 12 })
}
build() {
Column() {
this.CardItem('分布式数据', '跨设备数据同步方案')
this.CardItem('分布式调用', '远程服务调用机制')
}
.padding(16)
}
}
3.2.3 链式调用与布局
ArkTS 的 UI 写法是链式的。每个组件后面可以跟一串 .属性() 和 .事件()。布局容器主要有 Column(纵向)、Row(横向)、Flex(弹性)、Stack(层叠)。
Row() {
Image($r('app.media.icon'))
.width(40)
.height(40)
.borderRadius(20)
Column() {
Text('设备名称').fontSize(16)
Text('在线').fontSize(12).fontColor('#52c41a')
}
.margin({ left: 12 })
}
.padding(12)
.backgroundColor('#fff')
.borderRadius(12)
.shadow({ radius: 4, color: '#0000001a' })
3.3 状态管理机制
状态管理是 ArkTS 最值得花时间理解的部分。说白了,就是「数据变了,UI 怎么跟着变」。我刚开始做鸿蒙项目时,就因为状态没搞对,界面死活不刷新。后来发现是装饰器用错了。
3.3.1 @State:组件内部状态
@State 是最常用的。它标记的变量一旦变化,组件会重新渲染。
@Entry
@Component
struct Counter {
@State count: number = 0
build() {
Column() {
Text(`计数: ${this.count}`)
.fontSize(28)
Button('加一')
.onClick(() => {
this.count++
})
}
}
}
嗯,这里要注意:@State 只能装饰简单类型或普通对象。如果你装饰一个数组,修改数组元素时不会触发刷新。得用 Array<T> 配合展开运算符或 newArray 赋值。
@State 装饰了一个对象,然后直接修改对象的属性(比如 this.user.name = '新名字')。界面没变!因为 ArkTS 检测不到属性级别的变化。正确做法是重新赋值整个对象:this.user = { ...this.user, name: '新名字' }。
3.3.2 @Prop:父传子
子组件想接收父组件的数据?用 @Prop。它是单向数据流,子组件不能修改父组件的数据。
@Component
struct ChildComponent {
@Prop title: string
build() {
Text(this.title)
.fontSize(20)
}
}
@Entry
@Component
struct ParentComponent {
@State message: string = '来自父组件的数据'
build() {
Column() {
ChildComponent({ title: this.message })
}
}
}
3.3.3 @Link:双向绑定
如果子组件需要修改父组件的数据,用 @Link。它建立双向绑定,子组件改了,父组件也跟着变。
@Component
struct InputField {
@Link text: string
build() {
TextInput({ placeholder: '请输入...', text: this.text })
.onChange((value: string) => {
this.text = value
})
}
}
@Entry
@Component
struct FormPage {
@State inputValue: string = ''
build() {
Column() {
InputField({ text: $inputValue })
Text(`你输入的是: ${this.inputValue}`)
}
}
}
@Link 时要用 $ 符号,比如 $inputValue。这是 ArkTS 的语法糖,表示传递引用而不是值。
3.3.4 @Provide 和 @Consume:跨层级传递
当组件嵌套很深时,一层层传 @Prop 太麻烦。用 @Provide 在祖先组件提供数据,用 @Consume 在后代组件消费数据。不需要中间组件传递。
@Entry
@Component
struct GrandParent {
@Provide themeColor: string = '#1890ff'
build() {
Column() {
ParentComponent()
}
}
}
@Component
struct ParentComponent {
build() {
Column() {
ChildComponent()
}
}
}
@Component
struct ChildComponent {
@Consume themeColor: string
build() {
Text('主题色')
.fontColor(this.themeColor)
}
}
3.3.5 @Observed 和 @ObjectLink:深度监听
如果你有一个复杂对象,嵌套了好几层,想监听内部属性的变化怎么办?用 @Observed 装饰类,用 @ObjectLink 在子组件中引用。
@Observed
class UserModel {
name: string = ''
age: number = 0
}
@Entry
@Component
struct UserPage {
@State user: UserModel = new UserModel()
build() {
Column() {
UserDetail({ user: this.user })
Button('修改年龄')
.onClick(() => {
this.user.age++
this.user = this.user // 触发刷新
})
}
}
}
@Component
struct UserDetail {
@ObjectLink user: UserModel
build() {
Text(`姓名: ${this.user.name}, 年龄: ${this.user.age}`)
}
}
@State:组件自己的状态@Prop:父传子,单向@Link:父子双向绑定@Provide/@Consume:跨层级传递@Observed/@ObjectLink:深度监听复杂对象
好了,ArkTS 的基础就这些。你想想看,其实核心就是三件事:类型安全、声明式 UI、状态驱动。把这三点吃透了,后面写分布式应用就会顺手很多。
我个人建议,学完这一章后,打开 DevEco Studio 新建一个项目,把上面的代码挨个敲一遍。别光看,动手才是最快的。
公众号:蓝海资料掘金营,微信deep3321