HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(三):ArkTS 高效开发:TypeScript 核心与 API 23 新规

摘要:Stage 模型为我们搭好了“全场景”的骨架,但要在这副骨架上长出血肉,离不开 ArkTS 这门鸿蒙原生语言。它脱胎于 TypeScript,却又被 HarmonyOS 6.1.0 API 23 赋予了更严格的“安全基因”。今天,我们就用《灵犀厨房》的真实代码,把 ArkTS 的类型系统、装饰器和 API 23 新规一次性讲透——不但写得爽,还写得安全。


一、为什么 ArkTS 是“高效开发”的天花板?

很多从 JavaScript / TypeScript 转过来的开发者,一开始可能会觉得 ArkTS 只是“又一套 DSL”。但真正上手后,你会发现:ArkTS 在保留 TypeScript 灵活性的同时,把“类型安全”和“声明式 UI”焊死在了底层

在 HarmonyOS 6.1.0 API 23 下,ArkTS 的设计哲学更加清晰:

  • 严格的类型检查any 已被彻底禁用,所有变量必须显式或可推导的类型,从源头掐死运行时类型错误。
  • 声明式 UI:用 @State@Prop@Link 等装饰器驱动视图,告别命令式操作 DOM 的繁琐。
  • 专为全场景优化:组件、布局、状态管理都与 Stage 模型的无缝协作,让一次开发多端部署成为可能。

对我们《灵犀厨房》来说,这意味着:你可以用一份带类型的代码,同时约束手机、平板、智慧屏上的 UI 行为,任何数据流转都有“合同”可依

一句话总结:ArkTS 不是“换皮 TypeScript”,而是 HarmonyOS 6.1.0 为“高可靠、高性能全场景应用”量身打造的装甲车。


二、TypeScript 核心在 ArkTS 中的“鸿蒙化”表达

ArkTS 继承了 TypeScript 绝大部分精华,但为了适应方舟运行时的要求,做出了很多针对性的强化。我们需要的,不是从头学一遍 TS,而是搞清楚 哪些能力可以直接用,哪些被“精装修”过

2.1 基础类型的严格派

在 API 23 中,以下类型是你每天都会打交道的:

TypeScript 类型 ArkTS 行为
number 64 位双精度浮点数,完全一致。
string 不可变 UTF-16 字符串,一致。
boolean true / false,一致。
array 推荐 Array<T>T[],一致。
object 允许,但应优先使用接口/类代替。
any API 23 禁用。推荐用具体类型或 Object(当无可奈何时)。
unknown API 23 不推荐。用联合类型或泛型替代。

《灵犀厨房》立刻能用的改进:之前我们在 Index.ets 里写了一些 console.info 直接打印未知结构,现在就可以用明确的类型接口来约束数据。

2.2 接口与类:数据模型的“基因蓝图”

既然 any 被禁,用接口(interface)和类(class)定义数据结构,就成了最自然的选择。比如,后面我们要做的菜谱对象,可以这样定义:

// 定义菜谱的核心数据结构
export interface Recipe {
  id: number;
  name: string;
  cover: Resource;          // 图片资源用 Resource 类型
  ingredients: string[];
  steps: string[];
  tags: string[];
  calories: number;         // 千卡
}

用接口的好处是,任何地方用到 Recipe 都会被 IDE 检查,如果少了字段或类型不对,编译期就会报红。在 API 23 下,这几乎就是强制要求了。

类(class)同样强大,而且可以配合 @Observed 装饰器实现深层状态观察。我们后面会在“购物清单”章节再详细展开。

2.3 枚举:让魔法数字滚出《灵犀厨房》

上一篇文章中,我们用字符串 'sm''md''lg' 表示断点,这种方式其实不够安全——万一拼错,布局就崩了。ArkTS 支持数字枚举和字符串枚举,正好用它来规范断点。

// 定义断点枚举
enum Breakpoint {
  SM = 'sm',
  MD = 'md',
  LG = 'lg'
}

// 使用枚举替代魔法字符串
private getBreakpoint(width: number): Breakpoint {
  if (width >= 840) return Breakpoint.LG;
  if (width >= 600) return Breakpoint.MD;
  return Breakpoint.SM;
}

这样一来,任何接受断点的函数都可以直接声明为 bp: Breakpoint,拼写错误直接编译报错。

2.4 泛型:让函数活起来,但不乱来

从 API 23 起,ArkTS 支持泛型,这让工具方法可以保持优雅的类型推导。例如封装一个响应式数组操作的辅助函数:

function addItem<T>(arr: T[], item: T): T[] {
  return [...arr, item];
}

let tags: string[] = ['快手菜', '低脂'];
tags = addItem(tags, '下饭'); // 类型自动推断为 string[]

虽然《灵犀厨房》目前阶段还用不到太复杂的泛型,但提前建立意识,能让你后续封装能力时如虎添翼。


三、ArkTS 语言新规(API 23):红线与糖,一次讲明白

每到新 API 版本,HarmonyOS 都会给 ArkTS “收紧”一些安全带。踩准这些规则,不仅能通过编译,还能写出更健壮的代码

3.1 动态特性全部禁用

evalnew Function、动态 import 等 JavaScript 传统艺能,在 ArkTS 中完全消失。方舟编译器需要在编译时就确定所有代码结构,这些运行时动态执行的能力自然不可能存在。

对我们来说,这意味着:所有逻辑都必须是静态可分析的。遇到需要动态派发逻辑时,可以用 switch...case 或映射表(Map/Record)代替,没有例外。

3.2 顶级声明必须是类或组件

在 ArkTS 中,每个 .ets 文件都必须是一个声明文件,不允许有孤立在类外的执行代码(除了一些编译期常量)。例如,你不能在文件根作用域写一个 console.log('Hello'),但定义常量 const PI = 3.14 是可以的。

我们的《灵犀厨房》页面都包裹在 @Component 结构体或 UIAbility 类中,天然满足这一要求。

3.3 类型断言的受限与推荐做法

TypeScript 常用的 as 语法在 ArkTS 中受到限制:只能用于某些安全场景,例如 (value as string).length不允许使用 as any 绕过类型检查,因为 any 本身就不存在了。

当确实需要临时“宽泛类型”时,可以用 Object 过渡,然后迅速收窄:

function handleUnknown(input: Object): string {
  // 先当成 object 访问,然后使用 typeof 保护
  if (typeof (input as Record<string, Object>)?.name === 'string') {
    return (input as Record<string, Object>).name as string;
  }
  return 'Unknown';
}

不过,最好的策略还是从一开始就用明确的联合类型或接口

3.4 @State@Prop@Link 的严格规则

ArkUI 的状态管理装饰器在 API 23 下基本延续之前规则,但编译器检查更苛刻:

  • @State 修饰的变量必须是简单类型、类或数组,不能是 any,必须显式初始化。
  • @Prop 是从父组件单向同步的数据,子组件不能修改它。
  • @Link 是双向同步,但要求父组件对应字段必须是 @State@Provide 等可观察源。

这些规则让我们在《灵犀厨房》中传递页面状态时有了清晰的数据流,避免“谁改了谁不知道”的混乱。

3.5 新增的实用语法糖(API 23)

在严控安全的同时,API 23 也带来了一点甜头:

  • 可选链 ?. 完全支持:安全访问深层属性,不再需要层层 if 判空。
  • 空值合并 ?? 支持:非常适合给状态设默认值。
  • 展开运算符 ...:在数组和对象字面量中可用,但动态 key 扩展有限制,建议用在已知结构上。

例如《灵犀厨房》中设置默认标语:

@State slogan: string = '';
// ...
Text(this.slogan ?? '你的私人厨艺助手')

四、实战:用 ArkTS 新规重构《灵犀厨房》首页

光说不练假把式,我们来把上一章的 Index.ets 彻底改造一番,让它既能展示 ArkTS 最佳实践,又能给后续章节打好地基。

4.1 定义数据模型与枚举

entry/src/main/ets 下新建 model 文件夹,并创建 Recipe.ts(即使首页暂时不展示菜谱,也可以先定义):

// model/Recipe.ts
export interface Recipe {
  id: number;
  name: string;
  cover: Resource;
  ingredients: string[];
  steps: string[];
  tags: string[];
  calories: number;
}

同时,在 Index.ets 同一目录下创建 Breakpoint.ts

// Breakpoint.ts
export enum Breakpoint {
  SM = 'sm',
  MD = 'md',
  LG = 'lg'
}

代码修改后的项目结构:
在这里插入图片描述

4.2 用类型武装首页

现在我们改写 Index.ets,全面应用类型安全:

import { window, display } from '@kit.ArkUI';
import { Recipe } from '../model/Recipe';
import { Breakpoint } from './Breakpoint';

@Entry
@Component
struct Index {
  @State appName: string = '灵犀厨房';
  @State slogan: string = '你的AI私人厨艺助手';
  @State currentBreakpoint: Breakpoint = Breakpoint.SM;

  private windowClass: window.Window | null = null;
  private sizeChangeCallback: ((size: window.Size) => void) | null = null;
  private density: number = 1; // 屏幕密度,默认为1

  // 使用 vp 宽度判断断点
  private getBreakpoint(pxWidth: number): Breakpoint {
    const vpWidth = pxWidth / this.density;
    if (vpWidth >= 840) return Breakpoint.LG;
    if (vpWidth >= 600) return Breakpoint.MD;
    return Breakpoint.SM;
  }

  async aboutToAppear(): Promise<void> {
    try {
      // 1. 获取屏幕密度,用于 px → vp 转换
      const defaultDisplay = display.getDefaultDisplaySync();
      this.density = defaultDisplay.densityPixels;

      // 2. 获取窗口实例并读取初始尺寸
      this.windowClass = await window.getLastWindow(getContext(this));
      if (this.windowClass) {
        const rect: window.Rect = this.windowClass.getWindowProperties().windowRect;
        console.info(`初始窗口宽度: ${rect.width}px, 高度: ${rect.height}px, 密度: ${this.density}`);

        this.currentBreakpoint = this.getBreakpoint(rect.width);
        console.info(`初始断点: ${this.currentBreakpoint}`);

        // 3. 注册窗口尺寸变化监听
        this.sizeChangeCallback = (size: window.Size): void => {
          const bp: Breakpoint = this.getBreakpoint(size.width);
          this.currentBreakpoint = bp;
          console.info(`断点切换为: ${bp} (pxWidth: ${size.width})`);
        };
        this.windowClass.on('windowSizeChange', this.sizeChangeCallback);
      }
    } catch (err) {
      console.error('获取窗口实例失败', JSON.stringify(err));
    }
  }

  aboutToDisappear(): void {
    if (this.windowClass && this.sizeChangeCallback) {
      this.windowClass.off('windowSizeChange', this.sizeChangeCallback);
    }
  }

  build() {
    Flex({
      direction: this.currentBreakpoint === Breakpoint.SM ? FlexDirection.Column : FlexDirection.Row,
      wrap: FlexWrap.NoWrap,
      justifyContent: FlexAlign.Center,
      alignItems: ItemAlign.Center
    }) {
      Column() {
        Text('🍳')
          .fontSize(this.currentBreakpoint === Breakpoint.SM ? 80 : 120)
        Text(this.appName)
          .fontSize(this.currentBreakpoint === Breakpoint.SM ? 36 : 48)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF6B35')
      }
      .margin({ bottom: this.currentBreakpoint === Breakpoint.SM ? 30 : 0 })

      Column({ space: 15 }) {
        Text(this.slogan ?? '你的私人厨艺助手')
          .fontSize(18)
          .fontColor('#999999')

        Button('开始点菜')
          .fontSize(18)
          .backgroundColor('#FF6B35')
          .borderRadius(24)
          .padding({ left: 40, right: 40 })
      }
      .margin({ left: this.currentBreakpoint === Breakpoint.SM ? 0 : 40 })
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#FFF8F0')
  }
}

变化点解读

  • 引入 Breakpoint 枚举,整个文件再无裸字符串 'sm',想写错都难。
  • Recipe 接口定义了菜谱结构,虽还未使用,但为下一篇的智能推荐布局提前统一了数据口径。
  • @State 属性都有明确的类型和初始值,完全满足 API 23 的编译要求。
  • console.info 使用了模板字符串,可读性更好(这属于 ArkTS 保留的 ES6 特性)。
  • 所有变量都用 const 声明(能不变的就不给机会变),符合最新规范。

4.3 验证

在 Phone 模拟器运行,并打开多屏模式拖拽变化窗口大小,Log 输出如下:

com.annan...ikitchen  I     初始窗口宽度: 1280px, 高度: 2832px, 密度: 3.5
com.annan...ikitchen  I     初始断点: sm
com.annan...ikitchen  I     Ability onBackground
com.annan...ikitchen  I     断点切换为: sm (pxWidth: 1320)
com.annan...ikitchen  I     Ability onBackground
com.annan...ikitchen  I     Ability onForeground
com.annan...ikitchen  I     断点切换为: sm (pxWidth: 1084)

在这里插入图片描述
日志解读

  1. 初始窗口宽度: 1280px :这是 windowRect.width 返回的物理像素值(px),是系统最原始的数据,代码直接打印出来方便调试。
  2. 密度: 3.5 :该设备密度(densityPixels)为 3.5,意味着 1vp = 3.5px
  3. 断点判断过程(代码内暗箱操作)
    打印完 px 后,内部立即执行了:
    vpWidth = 1280 / 3.5366<600 → 断点 sm
    
    所以日志紧接着显示 初始断点: sm
    同理,后续 断点切换为: sm (pxWidth: 1320) 也是 1320/3.5 ≈ 377 < 600,判断为 sm。
    当前三块模拟屏的 vp 宽度均小于 600,断点统一显示 sm,布局为纵向居中。

一切符合预期,并且代码层级比之前干净得多——这就是类型安全带来的“开发爽感”。


五、本阶段总结与下篇预告

今天,我们深度结合《灵犀厨房》,把 ArkTS 高效开发的三大支柱——TypeScript 核心、类型安全、API 23 新规——系统梳理并落地到了代码里。你学到了:

  • 为什么 ArkTS 要禁掉 any:为全场景应用的可靠性上保险。
  • 如何用接口、枚举、泛型建强类型模型:让数据在页面间传递时有“法律条文”可循。
  • API 23 的关键变化:动态特性消失、类型断言受限、状态管理规则更严格。
  • 实战重构:用新规把首页代码改造成了类型安全、可维护的现代 ArkTS 实现。

下篇预告:类型的地基已经铺好,接下来就进入《灵犀厨房》最让用户上瘾的功能——“今日吃什么”智能推荐布局(上)。我们将用一整套响应式网格和个性化排序,把“吃饭焦虑”彻底踩在脚下。


粉丝专属福利:为感谢大家支持,点赞 + 收藏 本专栏任意文章,并在评论区留言“纯血鸿蒙,我准备好了”,私信我即可领取《HarmonyOS 6.0 安全技术白皮书》电子版!

本文源码地址https://gitee.com/sulongannababy/lingxi-kitchen (第3章代码已同步更新)

如果你发现本文还有任何不严谨之处,欢迎随时指出,我们一起共建最优质的 HarmonyOS 6.1 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬,我们下一篇见~

专栏完整大纲

说明:原第 2 篇《环境搭建与超级设备模拟器配置(API 23)》已合并至开篇词中,新加入第 39 篇《ArkUI 组件深度解析与自定义组件封装》,保证完整 40 篇体系。

序号 文章标题
1 开篇词:打造消除“吃饭焦虑”的《灵犀厨房》(含环境搭建)
2 Stage 模型项目结构与设备形态适配策略
3 ArkTS 高效开发:TypeScript 核心与 API 23 新规(已完成)
4 【首页开发】“今日吃什么”智能推荐布局(上)
5 【首页开发】“今日吃什么”智能推荐布局(下)
6 【食材识别】调用相机拍照与图像分析服务
7 【AI 推荐逻辑】基于偏好与食材生成推荐菜谱
8 【菜谱详情】沉浸式分步浏览页
9 状态管理与跨组件通信:实现食材勾选
10 【购物清单】一键生成并分组展示
11 【数据打通】访问 Health Kit 获取健康数据
12 【营养分析引擎】计算个性化卡路里建议
13 【智能厨电模拟】用代码模拟发现与控制设备
14 【分布式流转】手机选菜谱→平板看步骤→智慧屏播视频
15 超级设备模拟器实战:多设备交互调试技巧
16 【语音合成】烹饪步骤分步语音播报
17 【语音识别】实现“下一步”“重复”等声控操作
18 【手表协同】烹饪计时器流转至智能手表
19 通知系统:多设备推送提醒
20 【元服务】一键烹饪推荐原子化服务
21 【服务卡片】在桌面查看烹饪进度
22 多媒体:AVPlayer 嵌入教学视频
23 交互动效:转场、列表动画与趣味反馈
24 手势操作:滑动调整“火力大小”
25 深色模式与多主题开发
26 响应式布局:折叠屏与平板完美适配
27 并发优化:TaskPool 加速图片分析
28 【收藏与历史】使用 Relational Store 持久化数据
29 【个人中心】偏好设置与过敏源管理
30 【社区分享】用户菜谱分享与展示
31 应用权限管理与隐私保护最佳实践
32 性能调优:使用 DevEco Profiler 提升流畅度
33 自动化测试与单元测试编写
34 签名与证书管理:真机调试准备
35 打包与发布:生成 Release 包与混淆
36 上架 AppGallery Connect 全程指南
37 数据分析:集成分析服务查看用户行为
38 ArkUI 组件深度解析与自定义组件封装(新增)
39 社区推广:如何利用 CSDN 等平台推广你的鸿蒙应用
40 专栏总结:回顾 40 篇,开启你的 HarmonyOS 生态新征程
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐