TypeScript 1.5现在有装饰器了。

谁能提供一个简单的示例,演示实现装饰器的正确方法,并描述可能有效的装饰器签名中的参数的含义?

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;

此外,在实现装饰器时,是否应该牢记一些最佳实践考虑因素?


class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}

目标:类的原型在上面的例子中它是"Foo" propertyKey:被调用方法的名称,在上面的例子中是"Boo" function(name){返回'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;
  }     
}

我最后摆弄了一下装饰器,并决定在任何文档发布之前为任何想要利用这一点的人记录我发现的东西。如果你发现任何错误,请随意编辑。

一般分

在声明类时调用装饰器,而不是在实例化对象时调用装饰器。 可以在同一个类/属性/方法/参数上定义多个装饰器。 构造函数上不允许使用装饰符。

一个有效的装饰符应该是: 可赋值给其中一个Decorator类型(ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator)。 返回一个值(在类装饰器和方法装饰器的情况下),该值可分配给被装饰的值。 参考


方法/形式访问器装饰器

实现参数:

target:类的原型(Object)。 propertyKey:方法的名称(字符串|符号)。 描述符:TypedPropertyDescriptor——如果你不熟悉描述符的键,我建议你在Object.defineProperty的文档中阅读它(它是第三个参数)。

示例-无参数

Use:

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");

输出:

方法参数是:["testing"] 返回值为:Message——testing

注:

Do not use arrow syntax when setting the descriptor's value. The context of this will not be the instance's if you do. It's better to modify the original descriptor than overwriting the current one by returning a new descriptor. This allows you to use multiple decorators that edit the descriptor without overwriting what another decorator did. Doing this allows you to use something like @enumerable(false) and @log at the same time (Example: Bad vs Good) Useful: The type argument of TypedPropertyDescriptor can be used to restrict what method signatures (Method Example) or accessor signatures (Accessor Example) the decorator can be put on.

示例-带参数(Decorator Factory)

使用实参时,必须声明带有装饰器形参的函数,然后返回带有示例签名的不带实参的函数。

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;
    };
}

静态方法装饰器

类似于方法装饰器,但有一些不同:

它的目标参数是构造函数本身,而不是原型。 描述符是在构造函数而不是原型上定义的。


类的修饰符

@isTestable
class MyClass {}

实现参数:

target:声明装饰器的类(TFunction扩展Function)。

示例:使用元数据api存储类的信息。


房地产装饰

class MyClass {
    @serialize
    name: string;
}

实现参数:

target:类的原型(Object)。 propertyKey:属性名称(字符串|符号)。

示例:创建@serialize("serializedName")装饰器,并将属性名添加到要序列化的属性列表中。


参数装饰

class MyClass {
    myMethod(@myDecorator myParameter: string) {}
}

实现参数:

目标:类的原型(函数-函数似乎不再工作了。您现在应该在这里使用any或Object,以便在任何类中使用装饰器。或者指定你想要限制它的类类型) propertyKey:方法的名称(字符串|符号)。 parameterIndex: parameter在函数参数列表中的索引(number)。

简单的例子

详细的例子(年代)

Memoize decorator -方法,获取/设置访问器装饰器示例


我在其他答案中没有看到一个重要的事情:

装饰器工厂

如果希望自定义如何将装饰器应用于声明,可以编写装饰器工厂。Decorator Factory只是一个函数,它返回将由Decorator在运行时调用的表达式。

// 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章节。


TS修饰符:

TS装饰器允许在类上添加额外的功能。在创建类的任何实例之前,在声明时由装饰器更改类。

语法:

装饰器使用@符号声明,例如@metadata。TS现在将搜索相应的元数据函数,并自动为它提供几个参数,这些参数根据具体的装饰内容而不同(例如,类或类属性获得不同的参数)

这些参数在decorator函数中提供:

类的原型对象 属性键或方法名 PropertyDescriptor对象,看起来像这样 {可写:true,可列举:false,可配置:true,值:bool}

根据decorator的类型,这些参数中有1-3个被传递给decorator函数。

装饰器的类型:

下面的装饰器可以应用到一个类,TS将按以下顺序计算它们(下面的总和来自TS文档):

为每个实例成员应用参数装饰器,然后是方法、访问器或属性装饰器。 参数装饰器,后面跟着方法、访问器或属性 为每个静态成员应用装饰器。 参数装饰器应用于构造函数。 类decorator应用于类

更好地理解它们的最好方法是通过例子。注意,这些示例确实需要对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

类装饰器(来自TS文档):

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
 

装修商及装修厂:

decorator可以通过decorator函数或decorator factory函数来声明。在语法上有区别,最好通过一个例子来解释:

// 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

您还可以在typescript中为原始构造函数装饰/增强新功能(我使用3.9.7)。下面的代码片段包装了原始构造函数,为name属性添加前缀。这种情况发生在类被实例化的时候,而不是在声明类的时候!

 //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);       
        }
      };
    };
  }

当类实例化时,新的构造函数逻辑与原始的ctor逻辑一起运行

  @Prefixer('Mr')
  class Person {
    name = 'MBB';
  
    constructor() {
      console.log('original ctr logic here!');
    }
  }
  
  const pers = new Person();
  
  console.log(pers); //Mr.MBB