TypeScript的装饰器是一种特殊类型的声明,它可以附加到类声明、方法、访问符、属性或参数上,以实现对所修饰对象的行为的修改或增强。装饰器主要以函数的形式出现,运行在编译阶段,为开发者提供了一种以声明式方法将元信息添加至已有代码的方式。
若要启用实验性的装饰器特性,你必须在命令行或
tsconfig.json
里启用experimentalDecorators
编译器选项。
作用:通过使用装饰器,开发者可以在不修改代码自身的前提下,给已有代码增加额外的行为(通知),从而解决一些重复性的问题,例如验证、性能监控、数据绑定等。这使得代码更加灵活和可维护。
类装饰器
类型声明:
type ClassDecorator = <TFunction extends Function>
(target: TFunction) => TFunction | void;
- @参数:
target
: 类的构造器。
- @返回:
如果类装饰器返回了一个值,她将会被用来代替原有的类构造器的声明。 - 因此,类装饰器适合用于继承一个现有类并添加一些属性和方法。
我们定义一个类,我们想在这个类里面加新的属性和功能,但不能修改这块代码!
type Consturctor = { new (...args: any[]): any };
function toString<T extends Consturctor>(BaseClass: T) {
return class extends BaseClass {
toString() {
return JSON.stringify(this);
}
};
}
@toString
class C {
public foo = "foo";
public num = 24;
}
console.log(new C().toString())
注意 如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中 不会为你做这些。
属性装饰器
在Typescript中,属性装饰器可以接收两个参数:
- 目标类的原型(对于静态成员则为类的构造函数),
- 该属性的名称。
我们可以根据自己的需要来定制属性装饰器。来看以下例子:
function logAccess(target: any, propertyName: string): any {
let value = target[propertyName];
const getter = function () {
console.log(`Get ${propertyName}`);
return value;
};
const setter = function (newVal: any) {
console.log(`Set ${propertyName} to ${newVal}`);
value = newVal;
};
// 如果属性原本不可写,则不添加 setter
if (delete target[propertyName]) {
Object.defineProperty(target, propertyName, {
get: getter,
set: typeof value === 'object' ? setter : undefined, // 如果值是对象,则允许设置;否则,保持只读
enumerable: true,
configurable: true
});
}
}
class MyClass {
@logAccess
public myProperty: string = '';
}
const obj = new MyClass();
obj.myProperty = 'Hello, Decorators!'; // 输出: Set myProperty to Hello, Decorators!
console.log(obj.myProperty); // 输出: Get myProperty,并打印属性值
在这个例子中,logAccess
是一个属性装饰器函数。当它被应用到 MyClass
的 myProperty
属性上时,它会创建一个 getter 函数和一个 setter 函数(如果原始属性是可写的)。getter 函数在每次访问属性时打印一条消息,而 setter 函数在每次设置属性值时打印一条消息。通过 Object.defineProperty 方法,我们替换了原始的属性访问器,从而实现了对属性访问的追踪。
参数装饰器
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引。
function logParameter(target: any, propertyName: string, index: number) {
const method = target[propertyName];
target[propertyName] = function(...args: any[]) {
console.log(`Calling ${propertyName} with parameter at index ${index}: ${args[index]}`);
return method.apply(this, args);
};
}
class MyClass {
constructor(@logParameter public readonly name: string) {
}
greet(@logParameter name: string) {
return `Hello, ${name}!`;
}
}
const obj = new MyClass('Alice'); // 输出: Calling constructor with parameter at index 0: Alice
console.log(obj.greet('Bob')); // 输出: Calling greet with parameter at index 0: Bob,并返回 'Hello, Bob!'
在这个例子中,logParameter
是一个参数装饰器函数。它被应用于 MyClass
的构造器参数和 greet
方法的参数上。当构造器或方法被调用时,装饰器函数会首先被触发,并记录传入的参数值。然后,它调用原始的构造器或方法,并返回其结果。
方法装饰器
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
function logMethodCall(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) {
const originalMethod = descriptor.value;
// 修改方法的定义
descriptor.value = function(...args: any[]) {
console.log(`Calling method ${propertyName} with arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyName} returned:`, result);
return result;
};
return descriptor;
}
class MyClass {
@logMethodCall
greet(name: string) {
return `Hello, ${name}!`;
}
}
const obj = new MyClass();
obj.greet('Alice'); // 输出调用和返回信息
在这个例子中,logMethodCall
是一个方法装饰器函数。它接收三个参数:target
(类的原型),propertyName
(被装饰的方法名),以及 descriptor
(方法的属性描述符)。装饰器函数内部修改了方法的定义,添加了日志记录的逻辑。当 greet
方法被调用时,会先打印出调用信息和参数,然后执行原始的方法,并打印出返回值。