要点
ArkTS 不是”带装饰器的 TypeScript”,而是 TypeScript 收紧后的严格方言:动态特性被大面积禁止,严格类型检查不是可配置项而是语言的一部分。会 TS 的人迁移过来,语法负担很小,真正的坎是那批 arkts-* 约束规则和状态管理装饰器的观测边界。这篇按”差异清单 → lint 规则 → 装饰器 → 文件分工”的顺序把坎一个个过掉。
ArkTS 与 TypeScript 的关系
三层关系:ArkTS 基于 TypeScript,大量基础语法(class、interface、泛型、箭头函数、模块)原样可用;它删掉了一批动态特性(any、解构、for…in 等),目的是让代码可被 AOT(Ahead-of-Time)静态编译成机器码,换取运行时性能和启动速度;它又通过装饰器扩展出声明式 UI 与状态观测,这部分是 TS 没有的。官方文档里有一份《从 TypeScript 到 ArkTS 适配规则》,是本篇差异清单的出处,细节以它为准。
与 TS 的高频差异清单
| TS 写法 | ArkTS | 规则 ID | 怎么改 |
|---|---|---|---|
| any / unknown | 禁止 | arkts-no-any-unknown | 写具体类型;跨语言边界受限使用 ESObject |
| let p = { x: 1 }(无类型字面量) | 禁止 | arkts-no-untyped-obj-literals | 声明类型:let p: Point = { x: 1 },或用 class |
| type P = { x: number }(字面量当类型) | 禁止 | arkts-no-obj-literals-as-types | 改用 interface 或 class |
| 结构化类型(鸭子类型赋值) | 受限 | arkts-no-structural-typing | 显式 implements / extends 建立关系 |
| 交叉类型 A & B | 禁止 | arkts-no-intersection-types | 用继承组合出新类型 |
| 解构 const { a } = obj | 禁止 | arkts-no-destruct-decls / -assignment | 逐个赋值:const a = obj.a |
| 对象展开 {…obj} | 受限 | arkts-no-spread | 逐字段赋值;数组场景部分可用 |
| for…in、in 运算符 | 禁止 | arkts-no-for-in / arkts-no-in | 用 for…of 或显式判断 |
| function 表达式 | 禁止 | arkts-no-func-expressions | 一律用箭头函数 |
| function* 生成器 | 禁止 | arkts-no-generators | 没有直接等价物,改写普通循环/回调 |
| throw ‘str’(抛非 Error) | 禁止 | arkts-limited-throw | 只抛 Error 及其子类 |
| Partial 等工具类型 | 受限 | arkts-no-utility-types | 仅 Partial / Required / Readonly / Record 可用 |
| Reflect、动态改原型等 | 禁止 | arkts-limited-stdlib | 元编程能力整体不可用 |
| @ts-ignore | 禁止 | arkts-strict-typing-required | 不许用注释关检查,类型问题只能正面修 |
一个改写实例,感受一下”收紧”的力度:
// TypeScript 习惯写法,在 ArkTS 里逐条报错:
// const merge = (a, b) => ({ ...a, ...b }) // any 参数、无返回类型、展开、无类型字面量
// let cfg: { debug: boolean } = { debug: true } // 字面量类型
// ArkTS 合法写法:
interface AppConfig {
debug: boolean
}
const makeConfig = (debug: boolean): AppConfig => {
return { debug: debug } // 返回类型标了 AppConfig,字面量就有了归属
}
let cfg: AppConfig = makeConfig(true)
lint 规则在哪里、怎么读
这些约束不是可选的代码风格插件:DevEco Studio 对 .ets 文件实时检查,编译期同样强制。报错信息里会带规则 ID(社区文章常把禁 any 写成 arkts-no-any,官方迁移指南里的正式 ID 是 arkts-no-any-unknown,按 ID 搜索即可)。多数规则是错误级(不过编译),少数是警告级(如 arkts-limited-esobj 限制 ESObject 的使用),错误/警告的分级以当前 SDK 的迁移指南为准。遇到报错的第一反应不是绕过,而是按表格右侧的替代写法改——绕过手段(比如全用 ESObject)会让类型检查名存实亡。
状态管理装饰器:V1 全家
V1 是 API 9 时代沿用至今的状态管理体系,存量代码和大量三方库都在用:
| 装饰器 | 语义 | 关键约束 |
|---|---|---|
| @State | 组件内部状态,变化驱动本组件 UI 刷新 | 只观测第一层:数组项增删、对象整体赋值可见,嵌套属性变化不可见 |
| @Prop | 父到子单向同步,收到的是本地拷贝 | 子组件改它只影响自己;开销随数据变大(拷贝) |
| @Link | 父子双向同步 | 父组件传值必须加 $ 前缀(建立引用而非拷贝) |
| @Provide / @Consume | 跨层级注入与消费,免去逐层透传 | 同名匹配,重名会互相覆盖 |
| @Observed + @ObjectLink | 类实例 + 子组件接收,补上嵌套对象观测 | @ObjectLink 变量不能本地初始化,只能由父组件传入 |
状态管理 V2 与两套体系现状
V2 从 API 12 起逐步提供,解决 V1 的两个老大难:观测只到第一层、属性级刷新做不到。对应关系:
| V1 | V2 对应 | 变化 |
|---|---|---|
| @State | @Local | 名字即语义:组件本地状态 |
| @Prop / @Link | @Param + @Event | 数据下行显式只读,子改父必须走回调,双向绑定被拆开 |
| @Observed + @ObjectLink | @ObservedV2 + @Trace | 标注到类的属性级,嵌套对象的深层变化也能观测 |
| @Provide / @Consume | @Provider / @Consumer | 语义延续,机制重写 |
| — | @Monitor | 新增:声明式监听状态变化(回调可拿变化前后值) |
@ObservedV2
class Cart {
@Trace count: number = 0 // 标了 @Trace 的属性,改到第几层都刷新
}
@ComponentV2 // 注意:V2 组件用 @ComponentV2,不是 @Component
struct CartBadge {
@Local cart: Cart = new Cart()
build() {
Text(`${this.cart.count}`)
.onClick(() => { this.cart.count += 1 }) // 属性级变化,直接触发
}
}
现状要如实说:V2 是官方文档与迁移指南主推的方向,新项目建议直接上 V2;V1 没有被废弃,存量工程与三方库大量存在,短期内两套并存。硬约束是一条——V1 与 V2 不能混用:@Component 组件里写 @Local、@ComponentV2 组件里写 @State 都是直接报错。选型在一个工程内保持一致,别按组件心情混搭。
UI 层装饰器速览
@Component(组件,配 struct)、@Entry(页面根)、@Builder(无状态 UI 片段)在《第一个鸿蒙应用》里已用过。另外三个补充:@BuilderParam 让组件接收外部传入的 UI 片段(类似 Vue 的 slot);@Styles 抽取通用属性样式;@Extend 给特定组件扩展属性方法。入门记住前三个即可。
.ets、.ts 与 .d.ts 的分工
| 扩展名 | 角色 | 适用 |
|---|---|---|
| .ets | ArkTS 源文件,跑全部严格约束,可写声明式 UI | 工程内业务代码默认选择 |
| .ts | 标准 TypeScript,不受 arkts-* 约束,不能写 ArkUI | 从 Web 项目复用的纯逻辑、算法库 |
| .d.ts | 类型声明文件 | 给 .ts/.js 依赖补类型;系统 API 的类型声明也以声明文件形式提供 |
依赖方向有硬规则(arkts-no-ts-deps):.ets 可以 import .ts,.ts 不能 import .ets。所以”UI 层用 ets、纯逻辑下沉到 ts”的分层是自然结构;反过来想让一个 ts 工具文件回调 UI 状态,只能由 ets 侧把函数传进去。
常见问题
Q1: 一段 TS 代码粘进来满屏报错,从哪改起
按报错 ID 分类批量处理:先把 any 换成具体类型(arkts-no-any-unknown),再给所有对象字面量补类型归属(arkts-no-untyped-obj-literals),最后处理解构、展开这类结构性改动。前两类通常能消掉大半报错。
Q2: @State 嵌套对象不刷新,但我不想整体替换
这就是 V2 存在的理由:@ObservedV2 类 + @Trace 属性做属性级深度观测。V1 工程的等价方案是 @Observed + @ObjectLink 拆子组件。
Q3: @Component 里想用 @Param 报”装饰器不适用”
V1/V2 混用了。@Param 只属于 @ComponentV2。整个组件迁到 V2,或改用 V1 的 @Prop。
最佳实践与反模式
反模式一:把 TS 文件直接改后缀成 .ets 硬迁。约束是全量生效的,改后缀只会收获一份报错清单;正确做法是按 Q1 的顺序逐类修,或让纯逻辑留在 .ts 里被 ets 引用。
反模式二:拿 ESObject 当”更好的 any”到处用。ESObject 是为跨语言互操作预留的受限类型,超出边界使用会有警告,且丢掉类型检查的一切收益——等于在严格方言里重新过上无类型日子。
TS 本身的类型系统回顾(interface 与 implements、泛型约束、联合类型)可以翻《TypeScript 语法要点》——那些在 ArkTS 里依然成立,这篇讲的只是被收紧的部分。另一门”给 JS 系语言上严格枷锁”的对照样本是 Dart,见《Dart 语法要点》。
下一步
语言层过关后,日常开发缺的是查表:装饰器约束、组件参数、生命周期回调顺序、TaskPool 与 Worker 怎么选、高频报错对照——都在《ArkTS / ArkUI 基础知识参考字典》。
版本核实(撰写时检索,2025-01):ArkTS 为基于 TypeScript 的严格方言,严格类型检查不可配置、禁 any 与 @ts-ignore,规则 ID 与”仅 Partial/Required/Readonly/Record 可用”等细节出自官方《从 TypeScript 到 ArkTS 适配规则》(OpenHarmony/HarmonyOS 文档);状态管理 V2(@ComponentV2/@Local/@Param/@ObservedV2/@Trace/@Monitor 等)自 API 12 起提供,V1 仍被支持,两套不可混用;当前 Release SDK 配套 HarmonyOS NEXT 5.0(API 12)。规则分级与 API 可用性以 developer.huawei.com 官方文档为准。