2. ArkTS语言基础:TypeScript快速回顾、ArkTS语法特性、声明式UI编程思想
好,咱们进入第二章。说实话,很多同学一听到「语言基础」就觉得枯燥。但我得说,ArkTS 这块你如果底子打好了,后面写播放器界面会特别顺手。我自己刚接触 ArkTS 时也踩过一些坑,今天把这些经验都摊开来讲。
2.1 TypeScript 快速回顾——别怕,就这几个核心点
ArkTS 是基于 TypeScript 的,但又不是完全照搬。咱们先快速过一下 TS 里最常用的几个东西。你如果之前写过 JS,那这部分就是「复习 + 找不同」。
2.1.1 类型注解:给变量戴上「紧箍咒」
说白了,就是告诉编译器:「这个变量只能装数字,别给我塞字符串」。我在项目中遇到过好几次因为类型传错导致的诡异 bug,加了类型注解后,这类问题基本绝迹。
// 基础类型
let appName: string = '鸿蒙播放器';
let version: number = 1.0;
let isPremium: boolean = false;
// 数组和联合类型
let playlist: string[] = ['晴天', '七里香'];
let status: 'playing' | 'paused' | 'stopped' = 'stopped';
2.1.2 接口与类型别名:给数据「画个像」
写播放器肯定要处理歌曲信息。用接口定义好结构,后面所有地方都按这个来,团队协作时特别清爽。
interface Song {
id: number;
title: string;
artist: string;
duration: number; // 单位:秒
coverUrl?: string; // 可选属性
}
// 用类型别名定义函数签名
type PlayCallback = (song: Song) => void;
嗯,这里要注意:ArkTS 对 any 类型限制比较严。我建议你尽量少用 any,能用接口就用接口。曾经有个同事图省事全写 any,结果上线前一天爆了个类型错误,改得头皮发麻。
2.2 ArkTS 语法特性——和标准 TS 不一样的地方
ArkTS 是华为基于 TS 定制的。说白了,它砍掉了一些动态特性,加了一些声明式的东西。你想想看,在手机上跑应用,性能和安全是第一位的,所以它必须「收一收」。
2.2.1 强制类型:告别隐式转换
在标准 JS 里,1 + '2' = '12' 这种操作是合法的。但在 ArkTS 里,编译器会直接报错。为什么?因为这种隐式转换是很多 bug 的温床。
// ArkTS 中会报错
let a: number = 1;
let b: string = '2';
// let c = a + b; // ❌ 类型不兼容
// 正确做法:显式转换
let c: string = a.toString() + b; // '12'
2.2.2 装饰器:给类和方法「贴标签」
装饰器是 ArkTS 的一大亮点。你可以把它理解成给代码加「元数据」。比如在播放器里标记某个方法是「需要权限的」。
@Entry
@Component
struct PlayerPage {
@State currentTime: number = 0;
@RequirePermission('ohos.permission.MICROPHONE')
startRecording() {
// 录音逻辑
}
}
我个人觉得,装饰器让代码的意图变得特别清晰。你一看 @State 就知道这个变量是响应式的,一看 @Entry 就知道这是页面入口。
2.3 声明式 UI 编程思想——和传统方式说再见
这是 ArkTS 最核心的思想。传统方式(比如 Java 的 XML 布局)是「命令式」的:你要先找到按钮,再设置点击监听,再更新文本。声明式则是:你只管描述 UI 长什么样,状态变了 UI 自动更新。
2.3.1 核心公式:UI = f(state)
这个公式我建议你刻在脑子里。UI 是状态的函数。状态变了,UI 自动重新渲染。你不需要手动去操作 DOM 或组件树。
@Component
struct PlayButton {
@State isPlaying: boolean = false;
build() {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => {
this.isPlaying = !this.isPlaying;
})
}
}
你看,我只改了 isPlaying 的值,按钮文字自动就变了。不需要写 button.setText() 这种代码。我在做第一个播放器原型时,用声明式只花了 2 小时就搭好了基本界面,换成以前至少半天。
2.3.2 @State 和 @Prop:组件间的「数据通道」
声明式 UI 里,数据流向是单向的。父组件通过 @Prop 把数据传给子组件,子组件通过事件把变化传回去。这个模式在播放器里特别常用。
// 父组件
@Component
struct PlayerContainer {
@State currentSong: Song = { id: 1, title: '晴天', artist: '周杰伦', duration: 240 };
build() {
Column() {
SongInfo({ song: this.currentSong })
ControlPanel({ song: this.currentSong })
}
}
}
// 子组件
@Component
struct SongInfo {
@Prop song: Song;
build() {
Text(this.song.title)
.fontSize(20)
}
}
@Prop 的值。如果想改,必须通过父组件传进来的回调函数。这个约束一开始可能觉得麻烦,但用久了你会发现,数据流变得特别可预测,调试起来很爽。
2.3.3 状态管理的「三驾马车」
在播放器这种复杂应用里,状态管理是重中之重。ArkTS 提供了三个核心装饰器:
| 装饰器 | 作用范围 | 典型用途 |
|---|---|---|
@State |
组件内部 | 播放/暂停状态、当前进度 |
@Prop |
父→子传递 | 歌曲信息、封面URL |
@Link |
父子双向同步 | 播放列表选中项 |
我个人建议:能用 @State 就别用 @Link。双向绑定虽然方便,但数据流一复杂就容易乱。播放器里我一般只在「播放列表」和「播放器控制栏」之间用 @Link,其他地方都用单向数据流。
2.4 实战小例子:一个简单的播放器卡片
咱们把今天学的串起来,写一个播放器卡片组件。它显示歌曲名、歌手,还有一个播放/暂停按钮。
@Component
struct MusicCard {
@State isPlaying: boolean = false;
private song: Song;
build() {
Row() {
// 左侧:歌曲信息
Column() {
Text(this.song.title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(this.song.artist)
.fontSize(14)
.fontColor('#666')
}
.layoutWeight(1)
// 右侧:播放按钮
Button() {
Image(this.isPlaying ? $r('app.media.pause') : $r('app.media.play'))
.width(24)
.height(24)
}
.onClick(() => {
this.isPlaying = !this.isPlaying;
// 这里可以触发播放逻辑
})
}
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
}
这个例子虽然简单,但包含了我们今天讲的所有核心概念:类型注解、装饰器、声明式 UI、状态管理。你把这个吃透了,后面写完整的播放器界面就会很顺畅。
@State 改成 @Prop 试试,看看编译会不会报错。报错就是学习的机会。
好,第二章就到这里。下一章咱们开始搭建播放器的项目骨架,到时候会用到今天讲的所有知识。记得把代码敲一遍,光看是学不会的。