如何实现打字稿装饰器?


207

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;

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


请注意:-)如果您想将A注入@Injectable装饰器中,请参阅
Anand Rockzz

我建议看一下该项目的多个示例。有多个装饰器-一些非常简单,有些可能难以理解:github.com/vlio20/utils-decorators
vlio20

Answers:


396

我最终与装饰员一起玩耍,并决定将我想出的内容记录下来,以便在发布任何文档之前为希望利用此功能的人提供帮助。如果发现任何错误,请随时进行编辑。

一般要点

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

有效的装饰器应为:

  1. 可分配给装饰器类型(ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator)之一。
  2. 返回可分配给修饰值的值(对于类修饰器和方法修饰器)。

参考


方法/形式访问器装饰器

实现参数:

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

输出:

方法参数为:[“ testing”]

返回值为:消息-测试

笔记:

  • 设置描述符的值时,请勿使用箭头语法。如果您这样做,上下文的上下文this将不是实例的上下文。
  • 修改原始描述符比通过返回新描述符覆盖当前描述符要好。这使您可以使用多个装饰器来编辑描述符,而不会覆盖其他装饰器的操作。这样做可以让您同时使用@enumerable(false)和类似的东西@log(例如:Bad vs Good
  • 有用:的type参数TypedPropertyDescriptor可用于限制可以放置装饰器的方法签名(Method Example)或访问者签名(Accessor Example)。

示例-带参数(装饰器工厂)

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

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

静态方法装饰器

与方法装饰器类似,但有一些区别:

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

类装饰器

@isTestable
class MyClass {}

实现参数:

  • target:装饰器的类在(TFunction extends Function)上声明。

用法示例:使用元数据api在类上存储信息。


物业装饰

class MyClass {
    @serialize
    name: string;
}

实现参数:

  • target:该类的原型(Object)。
  • propertyKey:属性的名称(string| symbol)。

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


参数装饰器

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

实现参数:

  • target:该类的原型(Function—似乎Function不再起作用。您现在应该在此处使用anyObject,以便在任何类中使用装饰器。或者指定要限制其使用的类类型)
  • propertyKey:方法的名称(string| symbol)。
  • parameterIndex:函数参数列表中的参数索引(number)。

简单的例子

详细的例子


您知道在哪里可以找到参数装饰器示例吗?我一直在尝试实现一个没有成功的方法github.com/Microsoft/TypeScript/issues/…–
Remo H. Jansen,

1
@OweRReLoaDeD我在参数装饰器下添加了一个示例,该示例仅注销传递给装饰器的内容。我不确定这是否有帮助。我目前无法想到一个很好的例子。
David Sherret

仅供参考,我在github上收集并调整了此信息:github.com/arolson101/typescript-decorators
arolson101

--experimentalDecorators标志必须设置才能使此示例正常工作
Trident D'Gao

对于“ target或” prototype of the class和“ key指”,我有些困惑,请问有人可以详细说明吗?
Satej S'S

8

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

装饰厂

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

// 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手册装饰器一章


4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • 目标:在上述情况下,该类的原型为“ Foo”
  • propertyKey:所调用方法的名称,在上述情况下为“ Boo”
  • 描述符:对象的描述=>包含值属性,而值属性又是函数本身:function(name){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;
  }     
}

1
要使用严格的编译器设置进行编译,这是一项艰巨的任务
PandaWood '17

实际上,这是错误的并且无法编译,在返回{value:...}之后需要直接使用花括号。这甚至可以从潜在的代码来源中看到-blog.wolksoftware.com/…–
PandaWood
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.