Harmony os——ArkTS 语言笔记(七):注解(Annotation)实战理解

ArkTS 语言笔记(七):注解(Annotation)实战理解

鸿蒙第四期活动

这一篇专门整理 ArkTS 里的 注解系统(Annotation)

不是逐字翻译官方文档,而是把重点捋清楚,顺便加一点"实战感"的想法:注解到底能干嘛、能不能乱用、哪些地方会踩坑。


一、注解是啥?一句话版本

注解 = 在代码上贴"元信息标签",让编译器/框架在编译或运行时根据这些标签做额外处理。

和类型不同:

  • 类型是为了"约束值"的形状;
  • 注解是为了"描述声明的额外信息",比如:作者、版本、日志埋点、AOP 插桩等。

在 ArkTS 里,注解是语言级特性,使用的是 @interface 关键字,不是 TypeScript 那套 decorator


二、注解的基本语法:声明 + 使用

2.1 声明一个注解

@interface 声明:

ts 复制代码
@interface ClassAuthor {
  authorName: string;
}

注意几点:

  • 语法上看着像 interface,但前面多了一个 @
  • 这不是类型接口,而是"注解的结构定义"。

2.2 使用注解:贴在声明前面

复制代码
@ClassAuthor({ authorName: 'Bob' })
class MyClass {
  // ...
}

规则:

  • 注解一定要写在声明之前(类声明、方法声明前);
  • 使用时必须以 @ 开头:@ClassAuthor(...)
  • @ 和注解名之间不能有空格或换行

这些写法都是错的:

复制代码
ClassAuthor({authorName: 'Bob'});   // ❌ 没有 @ 前缀
@ ClassAuthor({authorName: 'Bob'}); // ❌ @ 和名称之间有空格

注解本身也可以 export,然后在别的文件里 import 再使用(后面会详细说)。

2.3 一个声明可以挂多个注解

复制代码
@MyAnno()
@ClassAuthor({ authorName: 'John Smith' })
class MyClass {
  // ...
}
  • 多个注解可以叠加使用;
  • 先后顺序目前对语义没有影响。

2.4 使用范围:只在 ArkTS 文件中生效

  • 注解只能在 .ets / .d.ets 文件中使用;
  • 这不是 TypeScript 的标准特性,在 .ts 中不能用。

三、关于 JS HAR + 混淆:一个隐藏的大坑

官方特地提醒了一句,很关键:

在 release + 混淆 的 JS HAR 中,不要用注解做 AOP 插桩。

原因是:

  • 编译产物是 JS 文件
  • JS 本身没有注解机制;
  • 编译过程中注解会被移除;
  • 你以为有 AOP,其实发布后的代码里什么都没有。

如果你:

  • 需要在 release 模式、且开了混淆;
  • 又想使用注解(比如 AOP、埋点、框架扩展等);

那就要:构建"字节码 HAR" ,而不是 JS HAR。

这个对做 SDK、公共库的人特别重要------否则线上行为会和本地调试的不一样。


四、用户自定义注解:能定义啥、不能定义啥?

API version 20 开始,支持用户自定义注解。

4.1 注解字段允许的类型

注解字段只允许下面几类:

  • number
  • boolean
  • string
  • 枚举(enum)
  • 以上类型的数组

不允许:

  • BigInt
  • 任意对象类型(比如 PersonDate 等)

例子:

复制代码
@interface MetaInfo {
  level: number;
  stable: boolean;
  author: string;
  role: MyEnum;
  tags: string[];
}

如果字段类型不是上面列出的,将会 编译报错


4.2 字段默认值:必须是"常量表达式"

默认值只能使用编译时就能确定的常量:

  • 数字字面量:1, 3.14
  • 布尔字面量:true, false
  • 字符串字面量:"foo"
  • 枚举值(前提是枚举值本身在编译期可确定)
  • 以上常量组成的数组

错误示例(枚举值不是编译期常量):

复制代码
// a.ts
export enum X {
  x = foo(); // ❌ 不是编译时常量
}

// b.ets
import { X } from './a';

@interface Position {
  data: number = X.x; // ❌ 注解字段默认值必须是常量表达式
}

简单理解:

注解元信息是要编译器在编译阶段就"吃进去"的,所以不能依赖运行时才能算出来的东西。


4.3 注解必须定义在顶层作用域

下面是错的:

复制代码
namespace ns {
  @interface MetaInfo {  // ❌ 注解不能定义在 namespace 里
    // ...
  }
}

注解只能定义在顶层,也就是模块的最外层。


4.4 注解命名不能和其他实体冲突

同一个作用域内,注解名不能和其他实体重名:

复制代码
@interface Position {
  // ...
}

class Position {  // ❌ 和注解重名
  // ...
}

重复声明同名注解也不行:

复制代码
@interface ClassAuthor {
  name: string;
}

@interface ClassAuthor {  // ❌ 重名
  data: string;
}

4.5 注解不是类型,不能当类型用

错误示例:

复制代码
@interface Position {}

type Pos = Position;  // ❌ 把注解当类型用,会编译错误

要记住:
@interface 声明的是"注解的结构",而不是"类型系统里可以用的普通类型"。


4.6 注解不能加在 getter / setter 上

下面这种写法会直接报错:

复制代码
@interface ClassAuthor {
  authorName: string;
}

@ClassAuthor({ authorName: 'John Smith' })
class MyClass {
  private _name: string = 'Bob';

  @ClassAuthor({ authorName: 'John Smith' }) // ❌ 不支持
  get name() {
    return this._name;
  }

  @ClassAuthor({ authorName: 'John Smith' }) // ❌ 不支持
  set name(authorName: string) {
    this._name = authorName;
  }
}

目前注解只允许用在 类声明方法声明 上(不包括 getter/setter)。


五、如何正确使用自定义注解?

5.1 定义注解

复制代码
@interface ClassPreamble {
  authorName: string;
  revision: number = 1;
}

@interface MyAnno {}

5.2 使用范围:只允许类和方法

示例:

复制代码
@ClassPreamble({ authorName: 'John', revision: 2 })
class C1 {
  // ...
}

@ClassPreamble({ authorName: 'Bob' })  // revision 使用默认值 1
class C2 {
  // ...
}

@MyAnno()  // 同一个注解可以同时用在类和方法上
class C3 {
  @MyAnno()
  foo() {}

  @MyAnno()
  static bar() {}
}

5.3 字段顺序无关紧要

下面两种写法等价:

复制代码
@ClassPreamble({ authorName: 'John', revision: 2 })
@ClassPreamble({ revision: 2, authorName: 'John' })

5.4 必须给"无默认值的字段"赋值

否则直接编译错误:

复制代码
@ClassPreamble()  // ❌ authorName 必填却没填
class C1 {
  // ...
}

赋值时要注意:

  • 类型要和注解中定义的字段类型一致;
  • 值也必须是"常量表达式"。

5.5 数组字段用数组字面量设置

复制代码
@interface ClassPreamble {
  authorName: string;
  revision: number = 1;
  reviewers: string[];
}

@ClassPreamble({
  authorName: 'Alice',
  reviewers: ['Bob', 'Clara'],
})
class C3 {
  // ...
}

5.6 没字段的注解可以省略括号

如果注解没有字段,可以这样写:

复制代码
@MyAnno
class C4 {
  // ...
}

六、注解的导入与导出:有点"挑剔"

6.1 导出注解:只能 export @interface

复制代码
export @interface MyAnno {}

目前只支持这种形式的导出。


6.2 导入注解:只支持两种方式

复制代码
// a.ets
export @interface MyAnno {}
export @interface ClassAuthor {}

// b.ets
import { MyAnno } from './a';
import * as ns from './a';

@MyAnno
@ns.ClassAuthor
class C {
  // ...
}

只允许:

  • import { MyAnno } from './a'
  • import * as ns from './a'

不允许:

复制代码
import { MyAnno as Anno } from './a'; // ❌ 不允许重命名注解
import type { MyAnno } from './a';    // ❌ 注解不允许使用 'type' 导入
// 以及任何其他奇怪 import/export 形式

6.3 只导入注解不会触发模块副作用

复制代码
// a.ets
export @interface Anno {}
export @interface ClassAuthor {}

console.info('hello');

// b.ets
import { Anno } from './a';
import * as ns from './a';

// 仅引用 Anno,不会执行 a.ets 里的 console.info
class X {
  // ...
}

这点挺有用:

只为了"拿注解定义",不会触发模块里的运行逻辑。


七、注解在 .d.ets 声明文件中的玩法

7.1 环境声明(ambient declaration)

可以在 .d.ets 声明文件中声明注解:

复制代码
// a.d.ets
export declare @interface ClassAuthor {}

含义:

  • 不是真的"定义"了一个注解;
  • 只是为已有的注解提供类型信息
  • 真正的注解实现要在其他源文件中定义。

实现文件要和声明完全一致

复制代码
// a.d.ets
export declare @interface NameAnno {
  name: string = '';
}

// a.ets
export @interface NameAnno {
  name: string = '';   // ✅ 保持一致
}

环境声明中的注解也可以被 import 使用:

复制代码
// a.d.ets
export declare @interface MyAnno {}

// b.ets
import { MyAnno } from './a';

@MyAnno
class C {
  // ...
}

7.2 编译器自动生成的 .d.ets 中的注解

两种情况:

1)导出的注解定义会保留
复制代码
// a.ets
export @interface ClassAuthor {}

@interface MethodAnno { // 没导出
  data: number;
}

// a.d.ets(生成)
export declare @interface ClassAuthor {}

只导出的注解会出现在 .d.ets 里。

2)实体上的注解实例,也有保留规则

保留注解实例要满足全部条件:

  1. 注解定义被导出(或者从别处导入的导出注解);
  2. 如果是类 → 这个类本身被导出;
  3. 如果是方法 → 所在类被导出,且方法不是私有的。

示例:

复制代码
// a.ets
import { ClassAuthor } from './author';

export @interface MethodAnno {
  data: number = 0;
}

@ClassAuthor
class MyClass {
  @MethodAnno({ data: 123 })
  foo() {}

  @MethodAnno({ data: 456 })
  private bar() {}
}

生成的 a.d.ets

复制代码
import { ClassAuthor } from './author';

export declare @interface MethodAnno {
  data: number = 0;
}

@ClassAuthor
export declare class MyClass {
  @MethodAnno({ data: 123 })
  foo(): void;

  bar; // 私有方法不保留注解
}

实际意义:

只有对外暴露的 API 上,那些"可见的"注解才会进入声明文件,方便下游使用。


7.3 开发者手写 .d.ets 时要注意

如果你手写 .d.ets,在声明里加注解,不会自动"反推回"实现文件。

复制代码
// b.d.ets
@interface ClassAuthor {}

@ClassAuthor
class C {
  // ...
}

// b.ets
@interface ClassAuthor {}

// 这里没写注解
class C {
  // ...
}

最终编译产物中,class C没有注解 的。

也就是说:实现文件为准,声明文件不会给你"补注解"。


八、重复注解、继承、抽象类上的注解

8.1 同一个实体不能重复使用同一注解

复制代码
@MyAnno({ name: '123', value: 456 })
@MyAnno({ name: '321', value: 654 }) // ❌ 编译错误:不允许重复注释
class C {
  // ...
}

8.2 子类不会继承父类的注解

  • 子类不会自动继承基类的注解;
  • 子类方法也不会继承父类方法上的注解。

如果需要注解,子类上要自己重新标。

8.3 抽象类和抽象方法不能用注解

复制代码
@MyAnno          // ❌ 不允许
abstract class C {
  @MyAnno        // ❌ 不允许
  abstract foo(): void;
}

这点比较严格:

注解只支持"具体类 / 具体方法",不支持抽象声明。


九、我自己的小总结 & 使用建议

  1. 把注解当"标签系统",不是当"类型系统"
    • @interface 声明的是"标签长什么样",而不是"值的类型";
    • 不要拿它到 type 里用,也不要和 TypeScript 的 interface 搞混。
  2. 字段类型 + 默认值要"极度简单"
    • 只用 number / boolean / string / enum / 数组
    • 默认值只用常量;
    • 这是为了让编译器在编译期就能把信息吃进去。
  3. 注意 HAR 类型和混淆问题
    • 做 SDK、做 AOP 的同学尤其要注意:
    • JS HAR + release + 混淆 → 注解会消失;
    • 真要靠注解做逻辑,就考虑字节码 HAR。
  4. 不要乱想"注解继承"
    • 子类不会继承父类注解,想用就再标一遍;
    • 对抽象类、抽象方法想加注解也不行,只能加在具体实现上。
  5. 导入导出非常克制
    • 只允许 export @interfaceimport { } / import * as
    • 不允许重命名、不允许 type、不允许花式导入导出。
相关推荐
听风吟丶1 小时前
Java 响应式编程实战:Spring WebFlux+Reactor 构建高并发电商系统
java·开发语言·spring
IT从业者张某某1 小时前
DAY2-Open Harmony PC 命令行适配指南(Windows版)-Tree命令行工具下载篇
harmonyos
_院长大人_1 小时前
在 CentOS 系统上使用安装并用alternatives切换 JDK17(与 JDK8 共存指南)
java·linux·运维·centos
嘴贱欠吻!1 小时前
开源鸿蒙-基于Flutter搭建GitCode口袋工具-2
flutter·华为·开源·harmonyos·gitcode
数新网络1 小时前
CyberAI多模态数据平台焕新升级!七大核心功能解锁高效管理新体验
java·网络·人工智能
Highcharts.js1 小时前
Renko Charts|金融图表之“砖形图”
java·前端·javascript·金融·highcharts·砖型图·砖形图
L***d6701 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
我命由我123451 小时前
Android 开发问题:布局文件中的文本,在预览时有显示出来,但是,在应用中没有显示出来
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
晚霞的不甘1 小时前
Flutter 与开源鸿蒙(OpenHarmony)扩展开发指南:自定义插件、系统能力封装与生态工具链建设
flutter·开源·harmonyos