有人可以提供一个简单的例子来演示实现装饰器的正确方法,并描述可能的有效装饰器签名中的参数是什么意思吗?
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Function, propertyKey: string | symbol, parameterIndex: number) => void;
此外,在实现装饰器时是否应该记住哪些最佳实践注意事项?
答案 0 :(得分:371)
我最终玩弄装饰,并决定记录我想知道的任何想要在任何文档发布之前利用它的人。如果您发现任何错误,请随时编辑此内容。
有效的装饰者应该是:
- 可分配给其中一种装饰器类型(
ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator
)。- 返回一个可赋值给装饰值的值(在类装饰器和方法装饰器的情况下)。
醇>
实施参数:
target
:类的原型(Object
)。propertyKey
:方法的名称(string
| symbol
)。descriptor
:TypedPropertyDescriptor
- 如果您不熟悉描述符的密钥,我建议您在Object.defineProperty
class MyClass {
@log
myMethod(arg: string) {
return "Message -- " + arg;
}
}
上阅读相关内容(这是第三个参数)。使用:
function log(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
const originalMethod = descriptor.value; // save a reference to the original method
// NOTE: Do not use arrow syntax here. Use a function expression in
// order to use the correct value of `this` in this method (see notes below)
descriptor.value = function(...args: any[]) {
// pre
console.log("The method args are: " + JSON.stringify(args));
// run and store result
const result = originalMethod.apply(this, args);
// post
console.log("The return value is: " + result);
// return the result of the original method (or modify it before returning)
return result;
};
return descriptor;
}
实现:
new MyClass().myMethod("testing");
输入:
@enumerable(false)
输出:
方法args是:[&#34; testing&#34;]
返回值为:消息 - 测试
注意:
@log
和TypedPropertyDescriptor
之类的内容(示例:The context of this
will not be the instance's if you do. vs Bad)class MyClass {
@enumerable(false)
get prop() {
return true;
}
}
function enumerable(isEnumerable: boolean) {
return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
descriptor.enumerable = isEnumerable;
return descriptor;
};
}
的类型参数可用于限制装饰器可以使用哪种方法签名(Good)或访问者签名(Method Example)穿上。使用参数时,必须使用装饰器参数声明一个函数,然后返回一个带有示例签名且不带参数的函数。
target
类似于具有一些差异的方法装饰器:
@isTestable
class MyClass {}
参数是构造函数本身,而不是原型。target
实施参数:
TFunction extends Function
:在(class MyClass {
@serialize
name: string;
}
)上声明装饰器的类。Accessor Example:使用元数据api存储类的信息。
target
实施参数:
Object
:类的原型(propertyKey
)。string
:媒体资源的名称(@serialize("serializedName")
| Example use)。 symbol
:创建class MyClass {
myMethod(@myDecorator myParameter: string) {}
}
装饰器并将属性名称添加到要序列化的属性列表中。
target
实施参数:
Function
:该类的原型(Function
- 似乎any
不再有效。您应该使用Object
或propertyKey
这里现在为了在任何类中使用装饰器。或者指定要限制它的类类型)string
:方法的名称(parameterIndex
| Example use)。number
:函数参数列表中的参数索引(daudio.src
)。答案 1 :(得分:8)
我在其他答案中没有看到一件重要的事情:
如果我们想要自定义装饰器如何应用于声明,我们可以编写一个装饰工厂。 Decorator Factory只是一个函数,它返回装饰器在运行时调用的表达式。
// This is a factory, returns one of ClassDecorator,
// PropertyDecorator, MethodDecorator, ParameterDecorator
function Entity(discriminator: string): {
return function(target) {
// this is the decorator, in this case ClassDecorator.
}
}
@Entity("cust")
export class MyCustomer { ... }
检查TypeScript手册Decorators chapter。
答案 2 :(得分:4)
class Foo {
@consoleLogger
Boo(name:string) { return "Hello, " + name }
}
您可以实现将每个调用记录到控制台的内容:
function consoleLogger(target: Function, key:string, value:any)
{
return value: (...args: any[]) =>
{
var a = args.map(a => JSON.stringify(a)).join();
var result = value.value.apply(this, args);
var r = JSON.stringify(result);
console.log('called method' + key + ' with args ' + a + ' returned result ' + r);
return result;
}
}
答案 3 :(得分:1)
TS装饰器允许在类上添加额外的功能。在创建类的任何实例之前,装饰者会在声明时间更改类。
装饰器以@
符号声明,例如@metadata
。 TS现在将搜索相应的元数据函数,并将自动为其提供sevaral参数,该参数随确切修饰的内容而异(例如,class或class属性获得不同的参数)
装饰器函数中提供了以下参数:
{writable: true, enumerable: false, configurable: true, value: ƒ}
根据装饰器的类型,这些参数中的1-3会传递给装饰器函数。
以下修饰符可以应用于类,TS会按以下顺序对其进行评估(以下总和来自TS文档):
更好地理解它们的最佳方法是通过示例。请注意,这些示例确实需要对TS语言和PropertyDescriptor
之类的概念有深入的了解。
function overwrite(
target: myClass,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log('I get logged when the class is declared!')
// desciptor.value refers to the actual function fo the class
// we are changing it to another function which straight up
// overrides the other function
descriptor.value = function () {
return 'newValue method overwritten'
}
}
function enhance(
target: myClass,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const oldFunc = descriptor.value;
// desciptor.value refers to the actual function fo the class
// we are changing it to another function which calls the old
// function and does some extra stuff
descriptor.value = function (...args: any[]) {
console.log('log before');
const returnValue = oldFunc.apply(this, args)
console.log('log after');
return returnValue;
}
}
class myClass {
// here is the decorator applied
@overwrite
foo() {
return 'oldValue';
}
// here is the decorator applied
@enhance
bar() {
return 'oldValueBar';
}
}
const instance =new myClass()
console.log(instance.foo())
console.log(instance.bar())
// The following gets logged in this order:
//I get logged when the class is declared!
// newValue method overwritten
// log before
// log after
// oldValueBar
function metaData(
target: myClass,
propertyKey: string,
// A Property Descriptor is not provided as an argument to a property decorator due to
// how property decorators are initialized in TypeScript.
) {
console.log('Execute your custom code here')
console.log(propertyKey)
}
class myClass {
@metaData
foo = 5
}
// The following gets logged in this order:
// Execute your custom code here
// foo
function seal(
constructor: Function,
) {
// Object.seal() does the following:
// Prevents the modification of attributes of
// existing properties, and prevents the addition
// of new properties
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@seal
class myClass {
bar?: any;
foo = 5
}
myClass.prototype.bar = 10;
// The following error will be thrown:
// Uncaught TypeError: Cannot add property bar,
// object is not extensible
装饰器。语法上的差异最好通过一个示例来说明:
// Returns a decorator function, we can return any function
// based on argument if we want
function decoratorFactory(arg: string) {
return function decorator(
target: myClass,
propertyKey: string,
) {
console.log(`Log arg ${arg} in decorator factory`);
}
}
// Define a decorator function directly
function decorator(
target: myClass,
propertyKey: string,
) {
console.log('Standard argument');
}
class myClass {
// Note the parentheses and optional arguments
// in the decorator factory
@decoratorFactory('myArgument')
foo = 'foo';
// No parentheses or arguments
@decorator
bar = 'bar';
}
// The following gets logged in this order:
// Log arg myArgument in decorator factory
// Standard argument
答案 4 :(得分:0)
您还可以为打字稿中的原始构造函数decorate/enhance
(我使用3.9.7)提供新功能。下面的代码片段包装了原始构造函数,以为name属性添加前缀。这是在类为instantiated
时发生的,而不是在类为declared
时发生的!
//Decorator function
function Prefixer(prefix: string) {
return function<T extends { new (...args: any[]): {name: string} }>(
originalCtor: T
) {
return class extends originalCtor {
constructor(..._: any[]) {
super();
this.name = `${prefix}.${this.name.toUpperCase()}`;
console.log(this.name);
}
};
};
}
当类为instantiated
时,新的构造函数逻辑将与原始ctor逻辑一起运行-
@Prefixer('Mr')
class Person {
name = 'MBB';
constructor() {
console.log('original ctr logic here!');
}
}
const pers = new Person();
console.log(pers); //Mr.MBB