在现代软件开发中,我们不断面临着为代码添加新功能而不改变其核心逻辑的需求。TypeScript 装饰器提供了一种优雅的解决方案,使我们能够以声明式的方式扩展类和方法的行为,而无需修改其原始代码。通过装饰器,我们能够改善代码的可读性和可维护性,实现诸如功能性增强、代码组织、错误处理等目标。值得注意的是,一些流行框架如 Angular、NestJS、TypeORM 等都是基于装饰器的设计原理,它们广泛地利用装饰器来简化开发过程,并提高代码的质量和可维护性。同时,控制反转(Inversion of Control)和容器(Dependency Injection Container)等设计模式也与装饰器息息相关,它们通过装饰器来实现依赖注入,提供了一种灵活的组件管理方式。本文将介绍 TypeScript 装饰器的相关用法以及实际应用场景。

1.准备工作

Decorator 装饰器是ES7 的一个新语法,目前为实验性特性,在未来的版本中可能会发生改变。
因此,想要在 TypeScript 中使用装饰器,首先要先开启这项实验性特性。具体操作为:
1.使用tsc --init生成初始化 ts 配置文件(确保已安装typescript为全局模块)
2.打开tsconfig.json文件开启装饰器选项:全局搜索 experimentalDecorators 设置为 true

2.装饰器分类

装饰器是一种特殊类型的声明,它能够被附加到类,方法,访问器,属性或参数上,被添加到不同地方的装饰器有不同的名称和特点。
下面将依次介绍以上分类的简单用法以及使用场景。

3.类装饰器

(1)定义一个装饰器

const CreatePerson: ClassDecorator = (target: Function) => {
    target.prototype.name='luoaoxuan'
    target.prototype.getAge=()=>{
        return 18
    }
}

(2)定义类并使用装饰器

@CreatePerson
class Person {
    constructor() {        
    }
}

类装饰器函数会把class Person的构造函数传入CreatePerson函数作为第一个参数。在紧贴类定义之前使用 @函数名 方式调用装饰器。

(3)测试

const person:any = new Person();
console.log(person.name,person.getAge())   // 输出 luoaoxuan 18

使用类装饰器,可以将类中方法与属性的定义交由装饰器来完成,而无需在类中进行定义,这便是 控制反转 的实现原理。

(4)装饰器工厂

使用一个外层函数返回一个装饰器函数的形式,由外层函数接收参数,提供给内层真正的的装饰器使用。

const CreatePerson=(name:string,age:number):ClassDecorator =>{  // 接收参数
    return (target: Function) => {  // 返回真正的装饰器函数
        target.prototype.name=name
        target.prototype.getAge=()=>{
            return age
        }
    }
}

@CreatePerson('luoaoxuan',18)  // 传递参数
class Person {
    constructor() {
        
    }
}
const person:any = new Person();
console.log(person.name,person.getAge()) // 输出 luoaoxuan 18

(5)装饰器组合

可以使用多个装饰器作用于同一个对象上:

const CreatePersonName=(name:string):ClassDecorator =>{
    return (target: Function) => {
        target.prototype.name=name
    }
}
const CreatePersonAge=(age:number):ClassDecorator =>{
    return (target: Function) => {
        target.prototype.getAge=()=>{
            return age
        }
    }
}

@CreatePersonName('luoaoxuan')
@CreatePersonAge(18)
class Person {
    constructor() {
        
    }
}
const person:any = new Person();
console.log(person.name,person.getAge()) // 输出 luoaoxuan 18

4.方法装饰器

方法装饰器接受三个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
成员的属性描述符。

const GetSum = (nums: number[]): MethodDecorator => {  // 外层接收参数数组
    return (target, propertyKey, descriptor: PropertyDescriptor) => {   // 内层返回真正的装饰器
        // console.log(target, propertyKey, descriptor);
        let fn = descriptor.value // 方法
        // 调用方法
        fn(nums.reduce((previousValue, currentValue) => previousValue + currentValue, 0))  // reduce求和
    }
}

class Utils {
    constructor() {

    }
    @GetSum([1, 2, 3, 4, 5, 6])
    getSum(result: number) {
        console.log(result);  // 输出 21
    }
}

这里实现一个数组求和的方法装饰器,我们同样将数组求和的方法定义交由装饰器来完成,在方法定义之前使用 @函数名 方式调用装饰器。

5.属性装饰器

属性装饰器接受两个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
属性的名字。

const SetName = (name: string): PropertyDecorator => {
    return (target: any, propertyKey) => {
        // console.log(target, propertyKey);
        target[propertyKey] = name  // 初始化 name 操作
    }
}
class Person {
    @SetName('luoaoxuan')
    name?: string;
    constructor() {
    }
}
const person = new Person();
console.log(person.name);  // 输出 luoaoxuan

这里实现一个数据初始化的属性装饰器,将数据的初始化交由装饰器来完成,在属性定义之前使用 @函数名 方式调用装饰器。

6.参数装饰器

(1)参数装饰器的简单实现

对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
参数在函数参数列表中的索引。

const Test = (): ParameterDecorator => {
    return (target, proptyName, index) => {
        console.log(target, proptyName, index); // 输出 {} create 0
    }
}

class A {
    constructor() {

    }
    create(@Test() name: string) {

    }
}

(2)参数装饰器与方法装饰器的配合使用

这里有如下要求:按照传入的key值从用户信息中解构得到相应的value值,如果没有传入key,则直接返回整个用户信息。需要使用方法装饰器与参数装饰器配合使用。

let tempKey: string   // 存储临时 key
const GetValue = (info: any): MethodDecorator => {
    return (target, propertyKey, descriptor: PropertyDescriptor) => {
        let fn = descriptor.value  // 方法
        fn(tempKey ? info[tempKey] : info)  // 有key则返回对应value  无key则返回整个对象
    }
}

const Value = (key?: string): ParameterDecorator => {
    return (target, PropertyKey, parameterIndex) => {
        tempKey = key as string
    }
}

class Person {
    constructor() {

    }
    @GetValue({ name: 'luoaoxuan', age: 18 ,id:'10000'})
    getNameValue(@Value('name') name?: string) {
        console.log(name)  // 输出 luoaoxuan
    }
    @GetValue({ name: 'luoaoxuan', age: 18 ,id:'10000'})
    getAllValue(@Value() obj?: Object) {
        console.log(obj)  // 输出 { name: 'luoaoxuan', age: 18, id: '10000' }
    }
}

参数装饰器常用于对象的解构,数据的过滤等操作,通常配合其他类型装饰器使用。