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';
我的小习惯:写组件 props 时,我习惯把所有可能的取值都用联合类型列出来。这样后面调用时,IDE 会自动提示,省得翻文档。

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'
避坑指南:我曾经在解析播放列表 JSON 时,忘了把字符串转数字,结果排序全乱了。记住:ArkTS 里没有「差不多」,类型必须一一对应。

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、状态管理。你把这个吃透了,后面写完整的播放器界面就会很顺畅。

我的建议:学 ArkTS 最好的方式就是动手改代码。把上面的 @State 改成 @Prop 试试,看看编译会不会报错。报错就是学习的机会。

好,第二章就到这里。下一章咱们开始搭建播放器的项目骨架,到时候会用到今天讲的所有知识。记得把代码敲一遍,光看是学不会的。