3. ArkTS语言基础:TypeScript快速回顾、ArkTS语法特性、声明式UI与状态管理、@State与@Prop装饰器

好,咱们直接进入正题。ArkTS 是鸿蒙应用开发的核心语言,它基于 TypeScript,但做了不少针对 UI 开发的优化。说白了,你如果会 TypeScript,上手 ArkTS 会非常快。但如果你之前只用过 JavaScript,也别慌,咱们一步步来。

3.1 TypeScript 快速回顾:为什么我们需要它?

我个人习惯把 TypeScript 看作是「带手铐的 JavaScript」。为什么这么说?因为它强制你定义类型。刚开始你可能觉得麻烦,但我在项目中遇到过无数次因为类型错误导致的线上 bug,从那以后我就成了 TypeScript 的忠实拥趸。

咱们快速过几个核心点:

  • 类型注解:变量后面加冒号指定类型,比如 let name: string = '鸿蒙'
  • 接口(Interface):定义对象的结构,比如 interface User { name: string; age: number }
  • 联合类型:一个变量可以是多种类型,比如 let id: string | number
  • 泛型:让你的代码更灵活,比如 function identity<T>(arg: T): T { return arg }
我的小技巧:刚开始写 TypeScript 时,如果不确定类型,可以先写 any 占位,等逻辑跑通了再回头补类型。但别养成习惯,any 用多了就失去 TypeScript 的意义了。

3.2 ArkTS 语法特性:它和 TypeScript 有什么不同?

ArkTS 保留了 TypeScript 的大部分语法,但做了减法。嗯,这里要注意,它去掉了 JavaScript 的一些动态特性,比如 any 类型和 var 声明。为什么?为了性能。鸿蒙的方舟编译器需要静态类型信息来做优化。

几个关键差异:

  • 强制类型安全:所有变量必须有明确的类型,不能随意改变
  • 装饰器支持:这是 ArkTS 的重头戏,后面会详细讲
  • UI 描述语法:用 @Component@Entry 来标记组件
  • 状态管理:通过装饰器实现响应式更新
// 一个简单的 ArkTS 组件示例
@Component
struct HelloComponent {
  @State message: string = 'Hello, HarmonyOS!'

  build() {
    Column() {
      Text(this.message)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Button('点击更新')
        .onClick(() => {
          this.message = '你点击了按钮!'
        })
    }
    .padding(20)
  }
}

你看,结构很清晰。@Component 标记这是一个组件,@State 标记这是一个状态变量,build() 方法里写 UI 描述。我个人觉得这种声明式写法比传统的命令式要直观得多。

3.3 声明式 UI 与状态管理:核心思想

声明式 UI 是什么?说白了,你只需要描述「UI 应该长什么样」,而不需要关心「怎么去更新它」。状态变了,UI 自动跟着变。你想想看,这比手动操作 DOM 省心多少?

在 ArkTS 里,状态管理的核心就是「数据驱动视图」。我刚开始接触这个概念时,总觉得有点玄乎,直到自己写了个计数器才恍然大悟。

核心原则:状态是 UI 的唯一数据源。任何时候,UI 都是状态的「投影」。你只需要修改状态,框架会自动帮你更新 UI。

举个例子,假设你有一个用户头像组件:

@Component
struct AvatarComponent {
  @State avatarUrl: string = 'default.png'

  build() {
    Image(this.avatarUrl)
      .width(100)
      .height(100)
      .borderRadius(50)
  }
}

avatarUrl 改变时,图片会自动刷新。你不用写任何更新 UI 的代码。这就是声明式的魅力。

3.4 @State 装饰器:组件的内部状态

@State 是 ArkTS 中最常用的装饰器之一。它标记的变量会成为组件的「响应式状态」。一旦这个变量发生变化,组件会重新渲染。

使用 @State 时要注意几点:

  • 只能用在组件内部,不能跨组件共享
  • 初始化时必须赋值
  • 修改时必须通过赋值操作(=),不能直接修改对象的属性
我曾经踩过的坑:有一次我直接修改了 @State 数组的某个元素,比如 this.list[0] = 'new value',结果 UI 没更新。后来才意识到,必须用新的数组替换旧数组,比如 this.list = [...this.list]。记住,不可变性是关键!
// 正确的做法
@State items: string[] = ['苹果', '香蕉', '橘子']

// 更新时
this.items = this.items.map((item, index) => {
  return index === 0 ? '草莓' : item
})

3.5 @Prop 装饰器:父子组件的数据传递

@Prop 用于从父组件接收数据。它和 @State 的区别在于:@Prop 的数据由父组件提供,子组件不能修改它(至少不能直接修改)。

我习惯把 @Prop 看作是「只读的输入参数」。父组件传什么,子组件就用什么。

// 父组件
@Component
struct ParentComponent {
  @State count: number = 0

  build() {
    Column() {
      ChildComponent({ value: this.count })
      Button('增加')
        .onClick(() => {
          this.count++
        })
    }
  }
}

// 子组件
@Component
struct ChildComponent {
  @Prop value: number

  build() {
    Text(`当前值:${this.value}`)
      .fontSize(18)
  }
}

你看,父组件通过 { value: this.count } 把数据传给子组件。子组件用 @Prop value: number 接收。当父组件的 count 变化时,子组件会自动更新。

我的建议:如果你的数据需要在子组件中修改,那就用 @State 配合回调函数。父组件传一个修改方法给子组件,子组件调用这个方法。这样数据流还是单向的,容易追踪。

3.6 实战小例子:一个可编辑的列表项

咱们把今天学的知识串起来,写一个简单的可编辑列表:

@Component
struct EditableListItem {
  @Prop initialText: string
  @State isEditing: boolean = false
  @State currentText: string = ''

  build() {
    Row() {
      if (this.isEditing) {
        TextInput({ placeholder: '输入内容', text: this.currentText })
          .onChange((value: string) => {
            this.currentText = value
          })
        Button('保存')
          .onClick(() => {
            this.isEditing = false
            // 这里可以触发回调通知父组件
          })
      } else {
        Text(this.initialText)
          .fontSize(16)
        Button('编辑')
          .onClick(() => {
            this.currentText = this.initialText
            this.isEditing = true
          })
      }
    }
    .padding(10)
  }
}

这个例子展示了 @Prop@State 的配合使用。initialText 从父组件传入,isEditingcurrentText 是组件内部状态。点击编辑按钮时,切换编辑模式,输入框绑定 currentText,实现双向数据绑定。

好了,这一章的内容就到这里。记住,状态管理是 ArkTS 的核心,@State@Prop 是你最常用的两个工具。多写几个小例子,慢慢就找到感觉了。