JS Decorator

Learned and introduced the basic usage of class decorators

如果你学习或者使用过 AngularNestJs,那么你一定会遇到大量的 @xxx 写法,比如:
Angular 里的组件定义:

ts 复制代码
@Component({
  selector: 'app',
  templateUrl: './app.component.html'
})
export class AppComponent {}

又比如 Nest Controller 中

ts 复制代码
@Controller('users')
export class UserController {
  @Get()
  findAll() {
    return 'all users';
  }
}

@xxx 究竟是什么?它们有什么用? 相信java开发者一定不会陌生,其实就是 Decorator 的使用。

什么是 Decorator ?

简单来说,装饰器的本质是函数,可以在类、方法、属性、参数等声明上添加额外的行为或元数据。参考

Decorator 的类型

TypeScript 支持以下几种类型的装饰器:类装饰器,方法装饰器,属性装饰器,参数装饰器
需要注意的是如果你的项目是 TS 项目,需要在 tsconfig 文件中开启编译选项

json 复制代码
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true

不然我们的装饰器会编译不过。

类装饰器

作用于类本身,用于观察、修改或替换类定义。
执行时机:在类定义时立即执行,而不是在实例化时执行。它接收的是类的构造函数

ts 复制代码
const doc: ClassDecorator = (constructor: Function) => {
    constructor.prototype.read = () => {
        console.log('I like reading books');
    };
}

@doc
class Slim {
    constructor() {
        console.log('Slim instance created');
    }
}

const one: any = new Slim();
one.read();
//Slim instance created
//I like reading books

属性装饰器

作用于类的实例属性或静态属性。
参数target 如果应用于实例属性那么它是类的原型对象, 如果应用于静态属性,则是类本身也就是构造函数。
propertyKey: string | symbol - 被装饰的属性的名称。
熟悉 Object.defineProperty的话,就很好理解了。

ts 复制代码
const readonly: PropertyDecorator = (target: Object, propertyKey: string | Symbol) => {
    Object.defineProperty(target, propertyKey as PropertyKey, {
        writable: false,
        configurable: false
    });
}

class Slim {
    @readonly
    name: string = 'Slim';
    constructor() {
        console.log('Slim instance created');
    }

}

const one: any = new Slim();
one.name = 'hhh'; //报错

方法装饰器

作用于类的实例方法或静态方法。
参数:

target: 同属性装饰器。

propertyKey: string | symbol - 被装饰的方法的名称。

descriptor: PropertyDescriptor - 方法的属性描述符。这是最关键的一个参数,它包含了方法的 value(方法本身)、writable、enumerable、configurable。比如我们可以通过在装饰器中改写 value实现一个LOG的装饰器:

ts 复制代码
const Log: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: any) => {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`调用 ${String(propertyKey)}, 参数:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`执行完${String(propertyKey)}:`, result);
        return result;
    };
    return descriptor;
};

class Slim {
    name: string;
    constructor() {
        console.log('Slim instance created');
        this.name = 'Slim';
    }

    @Log
    setName(name: string) {
        this.name = name;
        return "Set name to " + name;
    }
}

const one: any = new Slim();
one.setName("Tom");
//Slim instance created
//调用 setName, 参数: [ 'Tom' ]
//执行完setName: Set name to Tom

参数装饰器

作用于类的方法的参数(有点绕口)
参数:
target: 同属性装饰器。
propertyKey: string | symbol | undefined 如果装饰的是方法的参数,这是方法名。如果装饰的是构造函数的参数,则为undefined
parameterIndex: number - 参数在参数列表中的索引。

ts 复制代码
const DName: ParameterDecorator = (target, propertyKey, parameterIndex) => {
    console.log(`参数名:${String(propertyKey)},参数索引: ${parameterIndex}`);
};

class Slim {
    name: string;
    constructor() {
        console.log('Slim instance created');
        this.name = 'Slim';
    }

    setName(@DName name: string) {
        this.name = name;
    }
}

const one: any = new Slim();
one.setName("Tom");
//参数名:setName,参数索引: 0
//Slim instance created

Summary

我们对四种装饰器进行了实践,如果你想学习Angular或者Nest的话,Decorator 一定是前置知识。掌握装饰器能帮助我们更好地理解框架,写出更好的代码。