Administrator
发布于 2026-08-30 / 66 阅读
0
0

ArkTS 核心语法速通:TypeScript 开发者如何快速上手鸿蒙声明式开发

系列文章《鸿蒙开发从入门到专家》第 2 篇(初级篇)

前言

上篇我们搭好了环境,跑出了第一个 ArkUI 页面。但那个示例里的 @State、@Component、struct 到底是什么?如果你熟悉 TypeScript,会发现 ArkTS 的语法既亲切又陌生——亲切是因为它是 TypeScript 的超集,陌生是因为华为加入了一套声明式 UI 扩展和严格类型规则。

很多开发者抱着"我是 TS 老手,直接开干"的心态进场,结果在状态装饰器的作用域、严格模式的类型检查、Sendable 对象的限制上反复踩坑。本文的目标是:让有 TypeScript 基础的开发者,在 20 分钟内理清 ArkTS 与 TS 的核心差异,并掌握开发中真正高频使用的语法点。

一、ArkTS 与 TypeScript 的关系

ArkTS(Ark TypeScript)是华为基于 TypeScript 扩展的声明式编程语言。它保留了 TS 的全部类型系统和语法,同时增加了三块内容:

  1. 声明式 UI 语法:struct、@Component、@Builder 等,专门描述界面结构

  2. 状态管理装饰器:@State、@Prop、@Link、@Provide 等,实现数据驱动 UI

  3. 严格模式约束:对变量声明、类型推断、运行时行为有更严格的限制

1.1 严格模式:ArkTS 与 TS 最大的分水岭

ArkTS 默认启用严格模式,以下写法在标准 TS 里能跑,在 ArkTS 中会报错:

// ❌ ArkTS 报错:不允许使用 any
let data: any = fetchSomething()

// ❌ ArkTS 报错:对象字面量必须有类型标注
let person = { name: 'Tom', age: 25 }

// ❌ ArkTS 报错:不允许使用 undefined 初始化
let title: string = undefined

正确写法:

// ✅ 显式定义接口
interface Person {
  name: string
  age: number
}

let person: Person = { name: 'Tom', age: 25 }

// ✅ 使用联合类型代替 any
let data: string | number = fetchSomething()

// ✅ 可选属性用 ? 标记
let title: string | undefined = undefined

建议:新建项目时就在 build-profile.json5 中确认 strictMode: true,从一开始就养成类型安全的习惯。

1.2 类型推断增强(API 26 新特性)

HarmonyOS NEXT 5.0(API 26)对类型推断做了显著增强。最实用的是 as const 断言和类型守卫改进:

// as const 让推断结果更精确
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const

// config.apiUrl 的类型是 'https://api.example.com',而不是 string
// 这在匹配特定字符串字面量时非常有用

// 类型守卫改进:更智能的 narrowing
function processValue(value: string | number | boolean) {
  if (typeof value === 'string') {
    // value 在此作用域内被收窄为 string
    return value.toUpperCase()
  }
  // value 在此作用域内被收窄为 number | boolean
  return value
}

二、状态装饰器:ArkTS 的灵魂

状态管理是 ArkUI 声明式框架的核心。理解装饰器的作用域和传递规则,比学会任何组件都重要。

2.1 五大核心装饰器速查

装饰器

作用

数据流向

使用位置

@State

组件内部状态

内部自管理

组件内部

@Prop

父向子单向传递

父 → 子

子组件属性

@Link

父子双向同步

父 ↔ 子

子组件属性

@Provide / @Consume

跨层级共享

祖先 ↔ 后代

任意层级

@StorageLink

持久化到本地存储

绑定 LocalStorage

任意位置

2.2 @State:组件的私有状态

@Entry
@Component
struct CounterPage {
  @State count: number = 0
  @State userName: string = '访客'

  build() {
    Column() {
      Text(`${this.userName} 的计数器`)
        .fontSize(20)

      Text(`${this.count}`)
        .fontSize(48)
        .fontWeight(FontWeight.Bold)

      Button('增加')
        .onClick(() => {
          this.count++
          // 修改 @State 变量会自动触发 build() 重新执行
        })
    }
  }
}

关键规则:

  • @State 变量必须在声明时初始化

  • 修改必须通过赋值操作:this.count = this.count + 1(this.count++ 也可以)

  • 不能对 @State 对象的属性直接修改后期待刷新:this.user.name = 'Tom' 不会触发刷新,要写成 this.user = { ...this.user, name: 'Tom' }

2.3 @Prop 与 @Link:父子通信

// 父组件
@Entry
@Component
struct ParentPage {
  @State parentCount: number = 10

  build() {
    Column() {
      Text(`父组件: ${this.parentCount}`)
      
      // @Prop 单向传递:子组件修改不影响父组件
      ChildPropComponent({ countFromParent: this.parentCount })
      
      // @Link 双向同步:子组件修改会同步回父组件
      ChildLinkComponent({ countFromParent: $parentCount })
    }
  }
}

// 子组件 - @Prop 单向
@Component
struct ChildPropComponent {
  @Prop countFromParent: number

  build() {
    Button(`Prop: ${this.countFromParent}`)
      .onClick(() => {
        this.countFromParent++ // 只修改本地副本,父组件不受影响
      })
  }
}

// 子组件 - @Link 双向
@Component
struct ChildLinkComponent {
  @Link countFromParent: number

  build() {
    Button(`Link: ${this.countFromParent}`)
      .onClick(() => {
        this.countFromParent++ // 会同步修改父组件的 parentCount
      })
  }
}

注意 @Link 的传参语法是 $parentCount(带 $ 符号),这是 ArkTS 特有的语法糖,表示传递的是引用而非值拷贝。

2.4 @Provide / @Consume:跨层级状态共享

当组件嵌套很深时,逐层传递 @Prop 非常繁琐。@Provide 和 @Consume 可以实现"依赖注入"式的状态共享:

// 祖先组件提供状态
@Entry
@Component
struct AppRoot {
  @Provide('themeColor') themeColor: string = '#317AF7'
  @Provide('appFontSize') appFontSize: number = 16

  build() {
    Column() {
      SettingsPage()
    }
  }
}

// 深层子组件消费状态
@Component
struct DeepChildComponent {
  @Consume('themeColor') themeColor: string
  @Consume('appFontSize') appFontSize: number

  build() {
    Text('深层组件也能拿到主题色')
      .fontColor(this.themeColor)
      .fontSize(this.appFontSize)
  }
}

适用场景:主题配置、用户信息、全局开关等需要跨多层组件共享的数据。

注意:@Provide/@Consume 的数据只能在声明它的组件及其后代中使用,不能跨 Ability 共享。跨 Ability 要用 AppStorage 或事件总线。

三、ArkTS 并发:Taskpool 与 Worker

鸿蒙应用开发中,网络请求、大数据处理、图片编解码等耗时操作必须放在异步线程,否则会阻塞 UI 主线程。

3.1 Taskpool:官方推荐的首选方案

Taskpool 是 HarmonyOS NEXT 提供的任务并发框架,API 26 大幅增强了其能力:

import { taskpool } from '@kit.ArkTS'

// 定义可并发执行的任务函数
@Concurrent
function heavyComputation(data: number[]): number {
  let sum = 0
  for (let item of data) {
    sum += item * item
  }
  return sum
}

// 在 UI 组件中调用
async function runTask() {
  let data = Array.from({ length: 100000 }, (_, i) => i)
  
  // 提交任务到线程池
  let task = new taskpool.Task(heavyComputation, data)
  let result = await taskpool.execute(task) as number
  
  console.info(`计算结果: ${result}`)
}

Taskpool API 26 新增能力:

  • 任务组:将多个任务归为一组,统一等待结果

  • 串行队列:SequenceRunner 保证任务按提交顺序执行

  • 任务取消:支持取消尚未执行的任务

  • 任务优先级:新增 IDLE 级别表示后台任务

3.2 什么时候用 Worker?

Worker 适合需要长期运行、与主线程频繁通信的场景,比如后台音乐播放、实时数据处理。Taskpool 适合一次性、可并行的计算任务。简单原则:能用 Taskpool 就不用 Worker。

四、Sendable 对象:跨线程数据传递的关键

ArkTS 有一个让很多开发者困惑的概念:Sendable。当你尝试把对象传给 Taskpool 或 Worker 时,经常会遇到"对象不可发送"的报错。

4.1 什么是 Sendable?

Sendable 是 ArkTS 中可以在不同线程间安全传递的对象类型。基本类型(number、string、boolean)天然是 Sendable 的,但自定义对象需要满足特定条件。

4.2 让自定义类支持 Sendable

@Sendable
class UserProfile {
  name: string
  age: number
  tags: string[]

  constructor(name: string, age: number, tags: string[]) {
    this.name = name
    this.age = age
    this.tags = tags
  }
}

@Concurrent
function processUser(user: UserProfile): string {
  return `${user.name} 的年龄是 ${user.age}`
}

Sendable 类的约束:

  • 必须用 @Sendable 装饰器标记

  • 所有属性必须是 Sendable 类型(基本类型、Array、Map、Set、其他 Sendable 对象)

  • 不能使用 @State 等 UI 装饰器

  • 不能包含函数属性(方法可以,但有限制)

API 26 新增:Sendable 类型的 Function 支持,让回调传递更灵活。

五、实战:一个完整的登录表单

把上面学的知识串起来,写一个带表单验证的登录页面:

@Entry
@Component
struct LoginPage {
  @State phone: string = ''
  @State password: string = ''
  @State isLoading: boolean = false
  @State errorMsg: string = ''

  private isValidPhone(phone: string): boolean {
    return /^1[3-9]\d{9}$/.test(phone)
  }

  private async handleLogin() {
    if (!this.isValidPhone(this.phone)) {
      this.errorMsg = '请输入正确的手机号'
      return
    }
    if (this.password.length < 6) {
      this.errorMsg = '密码至少 6 位'
      return
    }

    this.errorMsg = ''
    this.isLoading = true

    try {
      // 模拟网络请求(实际项目用 http 模块)
      await new Promise<void>(resolve => setTimeout(resolve, 1500))
      console.info('登录成功')
    } catch (e) {
      this.errorMsg = '登录失败,请重试'
    } finally {
      this.isLoading = false
    }
  }

  build() {
    Column({ space: 16 }) {
      Text('用户登录')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 32 })

      TextInput({ placeholder: '手机号', text: $$this.phone })
        .type(InputType.PhoneNumber)
        .height(48)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)

      TextInput({ placeholder: '密码', text: $$this.password })
        .type(InputType.Password)
        .height(48)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)

      if (this.errorMsg) {
        Text(this.errorMsg)
          .fontSize(14)
          .fontColor('#E84026')
      }

      Button(this.isLoading ? '登录中...' : '立即登录')
        .width('100%')
        .height(48)
        .backgroundColor(this.isLoading ? '#999' : '#317AF7')
        .enabled(!this.isLoading)
        .onClick(() => this.handleLogin())
    }
    .width('100%')
    .padding(24)
    .backgroundColor(Color.White)
  }
}

注意 $$this.phone 语法:这是 ArkTS 提供的双向绑定语法糖,TextInput 的内容变化会自动同步到 @State 变量,无需手动写 onChange 回调。

小结与下篇预告

本文梳理了 ArkTS 的核心差异点:

  1. 严格模式是默认开启的,告别 any,拥抱显式类型

  2. 状态装饰器是 ArkUI 的灵魂,@State 管内部、@Prop/@Link 管父子、@Provide/@Consume 管跨层

  3. Taskpool 是并发首选,API 26 的任务组和串行队列能力很强大

  4. Sendable 是跨线程传递数据的门票,自定义类记得加装饰器

下一篇(第 3 篇)我们将进入 ArkUI 组件世界——Column、Row、Flex、List、Swiper、Tabs 等核心布局和容器组件的实战用法,以及如何搭建一个完整的应用页面结构。

参考阅读


评论