上一篇 初识nest 中大致了解了 nest
,同时可以看到不论是在 AppMoudle
的 @Module
或者是 AppController
的 @Controller
, 还是在 AppService
中的 @Injectable
, 不难发现,在 nest 中随处可见各种各样的装饰器
那么什么是装饰器呢?,又起到了什么作用呢
什么是装饰器
TypeScript 装饰器是一种特殊类型的声明,可以附加到类声明、方法、访问器、属性或参数上。装饰器提供了一种方式来修改类的行为或结构,而不需要改变类本身的代码。装饰器可以通过元编程的方式增强类的功能,使得代码更加灵活和可扩展。
简单来说,装饰器是一种非侵入式编程,不改变本身代码,是用来增强功能
类似于钢铁侠中的托尼斯塔克,可以佩戴不同的机甲作战,而本人不发生变化,不像绿巨人需要通过让自己变异来获取力量
一共有四种装饰器,分别是 类装饰器
,·方法装饰器
,属性装饰器
,参数装饰器
,它们都是由 @
开头,紧跟一个函数名,区别是函数参数不同
本文所有代码已放在文章最后
类装饰器
定义类装饰器 classDecorator
@classDecorator
class Person {}
类的装饰器类型为 ts 的内置类型
ClassDecorator
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | > void;
我们来定义 classDecorator
, 参数只有一个,当前实例
我们可以在他的原型上添加属性,所有的实例共用,比如添加 name
属性
const classDecorator:ClassDecorator = (target)=>{
target.prototype.name = "tsk";
}
那么在所有的实例上都可以获取到 name
属性
const tsk = new Person()
tsk.name // tsk
对于一些不需要动态属性的话,可以使用这种方法
很明显每个人的名字是不尽相同的,所以我们使用高阶函数创建
工厂函数
传入参数 tsk
@classDecorator('tsk')
class Person {}
使用高阶函数,接受参数并返回一个函数
const classDecorator = (name:string):ClassDecorator=>{
return (target) => {
target.prototype.name = name;
}
}
如果想要改变姓名,可以直接在装饰器中输入不同的参数即可
const tsk = new Person()
console.log(tsk.name) // tsk
方法装饰器
顾名思义对类中的方法做装饰
class Person {
@methodDecorator
showName() {
console.log("我是Tsk")
}
}
方法装饰器类型为
MethodDecorator
, 使用了泛型T
,可以传入Function
获取更好的提示
declare type MethodDecorator = <T>( target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void; interface TypedPropertyDescriptor<T> { enumerable?: boolean; configurable?: boolean; writable?: boolean; value?: T; get?: () => T; set?: (value: T) => void; }
我们定义名为 methodDecorator
的方法, 方法装饰器返回三个参数,分别是 原型对象
, 方法名
, 属性描述符
const methodDecorator: MethodDecorator = (target, key, descriptor) => {
console.log(target, key, descriptor);
};
打印出来看看
同样的,由于第一个参数是 原型对象,我们可以直接在上面加属性
比如添加 sex
属性:
const methodDecorator: MethodDecorator = (target, key, descriptor) => {
target.sex = "男"
};
在实例对象上可以直接使用
const tsk = new Person()
console.log(tsk.sex) // 男
第三个参数是属性描述符,描述符的 value 属性
即 被装饰的函数体
我们可以直接调用
const methodDecorator: MethodDecorator = (target, key, descriptor) => {
descriptor.value() // 我是Tsk
};
那么我们也可以修改函数体
const methodDecorator: MethodDecorator = (target, key, descriptor) => {
// 更改函数体
descriptor.value = function(){
console.log("我换名字了")
}
};
// ......
const tsk = new Person()
tsk.showName() // 我换名字了
根据这个特性,我们可以做切面编程
const methodDecorator: MethodDecorator = <Function>(target, key, descriptor) => {
let oldValue = descriptor.value;
descriptor.value = function(){
oldValue()
console.log(`调用了方法${key}`)
}
};
// ....
const tsk = new Person()
tsk.showName() // 我是Tsk,调用了方法showName
属性装饰器
依旧是我们熟悉的用法
class Person {
@ageDecorator
age: number;
constructor(age: number) {
this.age = age;
}
}
装饰器类型为 ts 的内置类型
PropertyDecorator
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
返回两个参数,分别是 原型对象
和 属性名
const ageDecorator:PropertyDecorator = (target, key) =>{
console.log(target,key) // Person, 'age'
target.sex = "男"
}
那么就在实例上可以使用
const person = new Person(20);
console.log(person.sex); // 男
在 nest 中的一个经典应用,使用 class-validator
校验类型
import {IsNumber} from "class-validator"
export class Person {
@IsNumber({ message:"taskId 必须是一个数字" })
taskId:number
}
如果类型不对,就会爆出taskId 必须是一个数字
错误
我们来实现一下,首先IsNumber
是一个高阶函数,接受 一个对象,使用属性访问器 对 key 进行 定义
参数装饰器
class Person {
show(@parameterDecorator name:string){}
}
参数装饰器类型为
ParameterDecorator
declare type ParameterDecorator = ( target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
实现 装饰器,返回三个参数,原型对象
,方法名
,参数位置下标
const parameterDecorator:ParameterDecorator = (target,key,index)=>{
console.log(target,key,index) // Peson,'show' ,0
}
不再过多介绍
总结
- 装饰器可以在不改变原有代码的情况下,对程序进行一定程度的增强
- 装饰器可以分为4类,分别是类装饰器,方法装饰器,属性装饰器,参数装饰器
- 类装饰器只有1个参数,是当前原型对象
- 方法装饰器有4个参数 原型对象,方法名,属性描述符
- 属性装饰器有2个参数,原型对象与属性名
- 参数装饰器有3个参数,原型对象,方法名,参数位置下标
在 NestJS 中使用装饰器用于增强和组织应用程序的各个方面,包括定义模块、控制器和服务,处理路由和请求参数,注入依赖,添加中间件和异常处理等,从而实现模块化、可维护和高度可扩展的应用程序架构。
后续会不断的介绍不同的装饰器在 nest 中的应用
本文所使用的所有代码
const classDecorator = (name: string): ClassDecorator => {
return (target) => {
target.prototype.name = name;
}
}
const methodDecorator: MethodDecorator = <Function>(target, key, descriptor) => {
let oldValue = descriptor.value;
descriptor.value = function () {
oldValue()
console.log(`调用了方法${key}`)
}
};
const IsNumber = ({ message }: { message: string }): PropertyDecorator => {
return (target, key) => {
// 获取当前值
let value = target[key];
const getter = function () {
return value;
};
const setter = function (newVal: number) {
if (typeof newVal !== 'number' || isNaN(newVal)) {
throw new Error(`${message}`);
}
value = newVal;
};
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
}
@classDecorator('tsk')
class Person {
@methodDecorator
showName() {
console.log("我是Tsk")
}
@IsNumber({ message: "必须是一个数字" })
age;
constructor(age: any) {
this.age = age;
}
}
const tsk = new Person("123")
tsk.showName() // tsk