系列文章《鸿蒙开发从入门到专家》第 2 篇(初级篇)
前言
上篇我们搭好了环境,跑出了第一个 ArkUI 页面。但那个示例里的 @State、@Component、struct 到底是什么?如果你熟悉 TypeScript,会发现 ArkTS 的语法既亲切又陌生——亲切是因为它是 TypeScript 的超集,陌生是因为华为加入了一套声明式 UI 扩展和严格类型规则。
很多开发者抱着"我是 TS 老手,直接开干"的心态进场,结果在状态装饰器的作用域、严格模式的类型检查、Sendable 对象的限制上反复踩坑。本文的目标是:让有 TypeScript 基础的开发者,在 20 分钟内理清 ArkTS 与 TS 的核心差异,并掌握开发中真正高频使用的语法点。
一、ArkTS 与 TypeScript 的关系
ArkTS(Ark TypeScript)是华为基于 TypeScript 扩展的声明式编程语言。它保留了 TS 的全部类型系统和语法,同时增加了三块内容:
声明式 UI 语法:
struct、@Component、@Builder等,专门描述界面结构状态管理装饰器:
@State、@Prop、@Link、@Provide等,实现数据驱动 UI严格模式约束:对变量声明、类型推断、运行时行为有更严格的限制
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 五大核心装饰器速查
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 的核心差异点:
严格模式是默认开启的,告别
any,拥抱显式类型状态装饰器是 ArkUI 的灵魂,
@State管内部、@Prop/@Link管父子、@Provide/@Consume管跨层Taskpool 是并发首选,API 26 的任务组和串行队列能力很强大
Sendable 是跨线程传递数据的门票,自定义类记得加装饰器
下一篇(第 3 篇)我们将进入 ArkUI 组件世界——Column、Row、Flex、List、Swiper、Tabs 等核心布局和容器组件的实战用法,以及如何搭建一个完整的应用页面结构。