如何实现打字稿装饰器?

Dav*_*ret 198 decorator typescript

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;
Run Code Online (Sandbox Code Playgroud)

另外,在实现装饰器时是否应该记住哪些最佳实践注意事项?

Dav*_*ret 386

我最终玩装饰员并决定记录我想出的任何想要在任何文档发布之前利用它的人.如果您发现任何错误,请随时编辑此内容.

一般要点

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

有效的装饰者应该是:

  1. 可分配给其中一个Decorator类型(ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator).
  2. 返回可赋值给装饰值的值(在类装饰器和方法装饰器的情况下).

参考


方法/正式访问者装饰器

实施参数:

  • target:类(Object)的原型.
  • propertyKey:方法的名称(string| symbol).
  • descriptor:一个TypedPropertyDescriptor-如果你不熟悉的描述符的钥匙,我会建议在阅读关于它这个文档Object.defineProperty(这是第三个参数).

示例 - 没有参数

使用:

class MyClass {
    @log
    myMethod(arg: string) { 
        return "Message -- " + arg;
    }
}
Run Code Online (Sandbox Code Playgroud)

执行:

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;
}
Run Code Online (Sandbox Code Playgroud)

输入:

new MyClass().myMethod("testing");
Run Code Online (Sandbox Code Playgroud)

输出:

方法args是:["testing"]

返回值为:消息 - 测试

笔记:

  • 设置描述符的值时,请勿使用箭头语法.如果你这样做,那么上下文this就不会是实例.
  • 修改原始描述符比通过返回新描述符覆盖当前描述符更好.这允许您使用多个装饰器来编辑描述符,而不会覆盖另一个装饰器所做的事情.这样做可以让你使用像@enumerable(false)@log在同一时间(例如: VS )
  • 有用:类型参数TypedPropertyDescriptor可用于限制可以放置装饰器的方法签名(方法示例)或访问者签名(访问者示例).

示例 - 使用参数(装饰工厂)

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

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;
    };
}
Run Code Online (Sandbox Code Playgroud)

静态方法装饰器

与具有一些差异的方法装饰器类似:

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

类装饰器

@isTestable
class MyClass {}
Run Code Online (Sandbox Code Playgroud)

实施参数:

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

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


物业装饰

class MyClass {
    @serialize
    name: string;
}
Run Code Online (Sandbox Code Playgroud)

实施参数:

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

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


参数装饰器

class MyClass {
    myMethod(@myDecorator myParameter: string) {}
}
Run Code Online (Sandbox Code Playgroud)

实施参数:

  • target:类的原型(Function-it似乎Function不再起作用了.你现在应该使用any或者Object在这里使用任何类中的装饰器.或者指定你想要限制它的类类型)
  • propertyKey:方法的名称(string| symbol).
  • parameterIndex:函数参数列表中的参数索引(number).

简单的例子

详细示例


Ond*_*žka 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 { ... }
Run Code Online (Sandbox Code Playgroud)

查看TypeScript手册装饰器章节.


Eri*_*ben 5

class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
Run Code Online (Sandbox Code Playgroud)
  • 目标:在上述情况下类的原型是“Foo”
  • propertyKey:被调用方法的名称,在上面的例子中是“Boo”
  • 描述符:对象的描述 => 包含 value 属性,而 value 属性又是函数本身: 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;
  }     
}
Run Code Online (Sandbox Code Playgroud)