简体   繁体   English

如何实现打字稿装饰器?

[英]How to implement a typescript decorator?

TypeScript 1.5 now has decorators . TypeScript 1.5现在有装饰器

Could someone provide a simple example demonstrating the proper way to implement a decorator and describe what the arguments in the possible valid decorator signatures mean?有人可以提供一个简单的例子来展示实现装饰器的正确方法并描述可能的有效装饰器签名中的参数的含义吗?

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;

Additionally, are there any best practice considerations that should be kept in mind while implementing a decorator?此外,在实现装饰器时是否应牢记任何最佳实践注意事项?

I ended up playing around with decorators and decided to document what I figured out for anyone who wants to take advantage of this before any documentation comes out.我最终玩弄了装饰器,并决定在任何文档出现之前为任何想要利用它的人记录我的想法。 Please feel free to edit this if you see any mistakes.如果您发现任何错误,请随时编辑。

General Points一般要点

  • Decorators are called when the class is declared—not when an object is instantiated.装饰器在声明类时调用——而不是在实例化对象时调用。
  • Multiple decorators can be defined on the same Class/Property/Method/Parameter.可以在同一个类/属性/方法/参数上定义多个装饰器。
  • Decorators are not allowed on constructors.构造函数上不允许使用装饰器。

A valid decorator should be:一个有效的装饰器应该是:

  1. Assignable to one of the Decorator types ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator ).可分配给装饰器类型之一( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator )。
  2. Return a value (in the case of class decorators and method decorator) that is assignable to the decorated value.返回一个可分配给装饰值的值(在类装饰器和方法装饰器的情况下)。

Reference参考


Method / Formal Accessor Decorator方法/正式访问器装饰器

Implementation parameters:实现参数:

  • target : The prototype of the class ( Object ). target :类的原型( Object )。
  • propertyKey : The name of the method ( string | symbol ). propertyKey :方法的名称( string | symbol )。
  • descriptor : A TypedPropertyDescriptor — If you're unfamiliar with a descriptor's keys, I would recommend reading about it in this documentation on Object.defineProperty (it's the third parameter). descriptor : A TypedPropertyDescriptor — 如果您不熟悉描述符的键,我建议您阅读有关Object.defineProperty 文档(这是第三个参数)。

Example - Without Arguments示例 - 不带参数

Use:用:

class MyClass {
    @log
    myMethod(arg: string) { 
        return "Message -- " + arg;
    }
}

Implementation:执行:

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

Input:输入:

new MyClass().myMethod("testing");

Output:输出:

The method args are: ["testing"]方法参数是:[“测试”]

The return value is: Message -- testing返回值为:Message——测试

Notes:笔记:

  • Do not use arrow syntax when setting the descriptor's value.设置描述符的值时不要使用箭头语法。 The context of this will not be the instance's if you do.如果您这样做,则this上下文将不是实例的上下文。
  • 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 )这样做允许您同时使用@enumerable(false)@log类的东西(例如: 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.有用TypedPropertyDescriptor的类型参数可用于限制装饰器可以放置的方法签名( 方法示例)或访问器签名(访问器示例)。

Example - With Arguments (Decorator Factory)示例 - 带参数(装饰工厂)

When using arguments, you must declare a function with the decorator's parameters then return a function with the signature of the example without arguments.使用参数时,您必须声明一个带有装饰器参数的函数,然后返回一个带有不带参数示例签名的函数。

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

Static Method Decorator静态方法装饰器

Similar to a method decorator with some differences:类似于方法装饰器,但有一些不同:

  • Its target parameter is the constructor function itself and not the prototype.它的target参数是构造函数本身而不是原型。
  • The descriptor is defined on the constructor function and not the prototype.描述符是在构造函数上定义的,而不是在原型上定义的。

Class Decorator类装饰器

@isTestable
class MyClass {}

Implementation parameter:实现参数:

  • target : The class the decorator is declared on ( TFunction extends Function ). target :装饰器声明的类( TFunction extends Function )。

Example use : Using the metadata api to store information on a class. 使用示例:使用元数据 api 存储类的信息。


Property Decorator物业装饰师

class MyClass {
    @serialize
    name: string;
}

Implementation parameters:实现参数:

  • target : The prototype of the class ( Object ). target :类的原型( Object )。
  • propertyKey : The name of the property ( string | symbol ). propertyKeypropertyKey的名称( string | symbol )。

Example use : Creating a @serialize("serializedName") decorator and adding the property name to a list of properties to serialize.使用示例:创建@serialize("serializedName")装饰器并将属性名称添加到要序列化的属性列表中。


Parameter Decorator参数装饰器

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

Implementation parameters:实现参数:

  • target : The prototype of the class ( Function —it seems Function doesn't work anymore. You should use any or Object here now in order to use the decorator within any class. Or specify the class type(s) you want to restrict it to) target :类的原型( Function似乎Function不再起作用。你现在应该在这里使用anyObject以便在任何类中使用装饰器。或者指定你想要限制它的类类型到)
  • propertyKey : The name of the method ( string | symbol ). propertyKey :方法的名称( string | symbol )。
  • parameterIndex : The index of parameter in the list of the function's parameters ( number ). parameterIndex :函数参数列表中parameterIndex的索引( number )。

Simple example 简单的例子

Detailed Example(s)详细示例

One important thing I don't see in the other answers:我在其他答案中没有看到的一件重要事情:

Decorator factory装饰厂

If we want to customize how a decorator is applied to a declaration, we can write a decorator factory.如果我们想自定义装饰器如何应用于声明,我们可以编写一个装饰器工厂。 A Decorator Factory is simply a function that returns the expression that will be called by the decorator at runtime.装饰器工厂只是一个返回表达式的函数,该表达式将在运行时被装饰器调用。

// 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 { ... }

Check the TypeScript handbook Decorators chapter .查看 TypeScript 手册装饰器章节

class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • target: prototype of the class in the above case it's "Foo"目标:上述情况下类的原型是“Foo”
  • propertyKey: name of the method called, in the above case "Boo" propertyKey:被调用方法的名称,在上面的例子中是“Boo”
  • descriptor: description of object => contains value property, which in turn is the function itself: function(name) { return 'Hello' + name;描述符:对象的描述 => 包含 value 属性,而 value 属性又是函数本身: function(name) { return 'Hello' + name; } }

You could implement something that logs each call to the console:您可以实现将每次调用记录到控制台的内容:

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

TS decorators: TS 装饰器:

TS decorators allow extra functionality to be added on a class. TS 装饰器允许在类上添加额外的功能。 The class is altered by decorators at declare time , before any instance of the class is created.在创建该类的任何实例之前,该类在声明时由装饰器更改。

Syntax:句法:

Decorators are declared with an @ sign, for example @metadata .装饰@metadata @符号声明,例如@metadata TS will now search for a corresponding metadata function and will automatically supply it with sevaral argument which vary on what is exactly decorated (eg class or class property get different arguments) TS 现在将搜索相应的元数据函数,并自动为它提供 sevaral 参数,这些参数因具体装饰的内容而异(例如,类或类属性获得不同的参数)

These parameters are supplied in the decorator function:这些参数在装饰器函数中提供:

  • The prototype object of the class类的原型对象
  • propertykey or method name属性键或方法名称
  • PropertyDescriptor object, looks like this {writable: true, enumerable: false, configurable: true, value: ƒ} PropertyDescriptor 对象,看起来像这样{writable: true, enumerable: false, configurable: true, value: ƒ}

Depending on the type of decorator 1-3 of these arguments are passed to the decorator function.根据装饰器的类型,将这些参数的 1-3 传递给装饰器函数。

Types of decorators:装饰器的类型:

The following decorators can be applied to a class and TS will evaluate them in the following order (following summation comes from TS docs):以下装饰器可以应用于类,TS 将按以下顺序评估它们(以下总和来自 TS 文档):

  1. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each instance member.每个实例成员都应用参数装饰器,然后是方法、访问器或属性装饰器。
  2. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each static member.每个静态成员都应用参数装饰器,然后是方法、访问器或属性装饰器。
  3. Parameter Decorators are applied for the constructor.参数装饰器应用于构造函数。
  4. Class Decorators are applied for the class类装饰器应用于类

The best way to understand them better is via examples.更好地理解它们的最好方法是通过示例。 Note that these example do need significant understanding of the TS language and concepts like PropertyDescriptor .请注意,这些示例确实需要对 TS 语言和PropertyDescriptor等概念有深入的了解。

Method decorators:方法装饰器:

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

Property decorators:属性装饰器:

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

Class decorators (from TS docs):类装饰器(来自 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
 

Decorators and decorator factories:装饰器和装饰器工厂:

decorators can be declared via decorators function or decorator factory functions.装饰器可以通过装饰器函数或装饰器工厂函数来声明。 There is a difference in syntax which is best explained via an example:语法上有区别,最好通过一个例子来解释:

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

You could also decorate/enhance new functionality to the original constructor in typescript(I used 3.9.7).您还可以在打字稿中decorate/enhance原始构造函数的新功能(我使用了 3.9.7)。 The below snippet wraps the original constructor to add prefix for name property.下面的代码片段包装了原始构造函数以添加 name 属性的前缀。 This happens when the class is instantiated instead when the class is declared !当类被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);       
        }
      };
    };
  }

when the class is instantiated the new constructor logic runs with original ctor logic -当类被instantiated ,新的构造函数逻辑与原始 ctor 逻辑一起运行 -

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM