前言
TS 在平时项目中会用的很多,但是 装饰器 一直没有用到过,最近在学 nestjs
,里面使用了大量的装饰器语法,今天来总结 TS装饰器
首先来看 装饰器 在 nestjs
中的使用
- 例子1
@Module({
controllers: [],
providers: [],
})
export class CatsModule {}
@Injectable()
class CatsService{
}
- 例子2
export class CatsService {
constructor( @InjectModel(Cat.name) private catModel: Model<Cat> ) {}
}
- 例子3
export class CatsController {
@Get("/findAll")
async findAll() {
return this.catsService.findAll();
}
@Post("/findGt")
async findGt(@Body("num") num: Number = 2) {
return this.catsService.findGt(num);
}
}
上面的 nest 代码分别用了类装饰器,参数装饰器,方法装饰器
使用装饰器可以让我们的代码变得简洁清晰, 今天我们来一探究竟
🔥 装饰器
一共有四种装饰器,分别是 类装饰器
,·方法装饰器
,属性装饰器
,参数装饰器
,它们都是由 @
开头,紧跟一个函数,区别是函数参数不同
✈️ 类装饰器
我们定义类装饰器 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")
}
}
装饰器类型为 ts 的内置类型 MethodDecorator
interface TypedPropertyDescriptor<T> {
enumerable?: boolean;
configurable?: boolean;
writable?: boolean;
value?: T;
get?: () => T;
set?: (value: T) => void;
}
declare type MethodDecorator = <T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
同样的,我们定义 methodDecorator
, 方法装饰器返回三个参数,分别是原型对象
, 方法名
, 属性描述符
const methodDecorator: MethodDecorator = (target, key, descriptor) => {
console.log(target, key, descriptor);
};
同样的,由于 第一个参数是 原型对象,我们可以直接在上面加属性
比如:
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 = (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 TodoDTO {
@IsNumber({ message:"taskId 必须是一个数字" })
taskId:number
}
如果类型不对,就会爆出taskId 必须是一个数字
错误
我们来实现一下,首先IsNumber
是一个高阶函数,接受 一个对象,使用属性访问器 对 key 进行 定义
const IsNumber = ({message}:{message:string })=>{
return (target: any, key: string)=> {
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,
});
}
}
class Person {
@IsNumber({ message:"必须是一个数字" })
age;
constructor(age:any) {
this.age = age;
}
}
const person = new Person(20);
console.log(person.age); // Output: 20
person.age = 30; // Valid age value
console.log(person.age); // Output: 30
person.age = "Invalid"; // Invalid age value
🚗 参数装饰器
class Person {
show(@parameterDecorator name:string){}
}
装饰器类型为 ts 的内置类型 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
}
❤️ 总结
TS 装饰器是一种强大的语言特性,可以让开发者在不改变原有代码结构的情况下扩展和修改类、方法或属性等元素的行为。它们可以用于实现诸如类型检查、依赖注入、日志记录、缓存等功能,使得代码更加模块化、可维护、易扩展。