TypeScript 装饰器

在之前的学习中,如果我们想给一个方法增加新功能,通常是直接修改那个方法的源码。但在复杂的企业级开发中,很多功能(比如打印日志、记录执行时间)是通用的,如果每个方法都手动去写一遍,代码会变得非常臃肿。

为了解决这个问题,TypeScript 引入了 “装饰器” 的概念。装饰器就像是一张 “神奇的标签”,我们只要把它贴在类或方法上面,就能在不破坏原有代码结构的前提下,赋予它们额外的能力。

提示: 除了 TypeScript,Python 也有装饰器的概念。

TypeScript 如何开启装饰器?

在学习装饰器之前,有一点大家要清楚:目前 TypeScript 中的装饰器仍然是一项 “实验性功能”。虽然装饰器在 NestJS、Angular 等顶级框架中已经被大规模使用,但 TypeScript 编译器默认是关闭它的。

如果直接写装饰器代码,你会看到满屏的红色报错。因此我们需要在项目的根目录下找到 tsconfig.json 文件,并开启以下配置:

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

其中 experimentalDecorators 是开启装饰器语法的开关;而 emitDecoratorMetadata 则是为了配合反射(Reflect)使用的,这两项在开发像 NestJS 那样的底层框架时非常重要。

提示: 关于 tsconfig.json,我们在后续 “TypeScript 配置文件” 一节中会详细介绍。

如果小伙伴们使用的是 TypeScript 5.0 及以上版本,此时会发现就算不开启 experimentalDecorators,直接使用装饰器也不会报错了。这是因为 TypeScript 5.0 已经正式实装了 ECMAScript 的 “标准装饰器”。

那么我们为什么还要开启 “实验性功能” 呢?主要原因有以下 2 点:

  • 生态依赖:目前市面上 99% 的顶级框架(如 NestJS、TypeORM、Angular)底层依然严重依赖 “实验性装饰器(Experimental Decorators)” 及其衍生的元数据反射能力(Metadata Reflection)。
  • API 差异:标准装饰器和实验性装饰器在底层的参数结构上是不兼容的。

因此,在当前的真实企业级开发环境中,全面开启 experimentalDecorators 依然是绝对的主流做法和行业标准。

TypeScript 装饰器是什么?

在 TypeScript 中,装饰器本质上就是一个普通的函数。我们可以通过 “@” 语法将其附加到类、方法、属性或参数上。

装饰器的核心思想是 “面向切面编程(AOP)”。简单来说,就是把那些与业务逻辑无关的功能(如日志、安全、性能统计)从业务代码中剥离出来,然后像 “切片” 一样横向注入进去。

TypeScript 装饰器

示例 1:一个最简单的类装饰器

// 定义装饰器函数:参数 target 就是被装饰的类本身
function Sealed(target: Function) {
    console.log("[系统]: 正在封闭类 ", target.name);
    Object.seal(target);    // 封闭类,禁止扩展新属性
}

// 使用装饰器函数
@Sealed
class User {
    public name: string = "Jack";
}

运行结果如下。

[系统]: 正在封闭类 User

分析:

在这个例子中,我们定义了一个名为 “Sealed” 的装饰器函数,然后通过 @Sealed 将其贴在 User 类上方。

当程序加载这个类时,装饰器函数会自动执行。然后你会发现,我们根本没有实例化 User 类,日志就已经打印出来了。这就是装饰器的特点:它在类定义时就会触发执行

TypeScript 装饰器工厂

在实际业务中,我们往往需要给装饰器传递一些参数。比如,想要指定日志的级别(INFO 或 ERROR)。这时候,我们就需要使用 “装饰器工厂” 了。

所谓的装饰器工厂,其实就是一个 “返回装饰器函数的函数”。

示例 2:实现带参数的日志装饰器

// 定义装饰器工厂
function Log(level: string) {
    // 返回真正的装饰器函数
    return function(target: Function) {
        console.log(`[${level}]: 正在监控类 ${target.name}`);
    };
}

@Log("INFO")
class Article {
    public title: string = "TypeScript 进阶之路";
}

@Log("WARNING")
class Admin {
    public role: string = "SuperAdmin";
}

运行结果如下。

[INFO]: 正在监控类 Article
[WARNING]: 正在监控类 Admin

分析:

通过这种 “闭包” 写法,我们可以给装饰器注入任意的配置信息。这种方式在开源框架源码中很常见,比如 NestJS 中的 @Controller("/user") 本质上就是一个装饰器工厂,那个 "/user" 字符串就是通过这种方式传进去的。

学习 TypeScript 装饰器,都要学什么?

装饰器是 TypeScript 中最能体现 “架构设计” 思想的功能之一。它让我们学会了如何把 “业务逻辑” 与 “辅助功能” 进行优雅地解耦。

虽然装饰器的语法看起来很魔幻,但小伙伴们只要记住一句话就行:装饰器,就是一个在特定时机被自动触发的函数

在接下来的几篇中,我们将由浅入深,逐一攻克装饰器的四大领地:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号