如何实现typescript装饰器?

如何实现typescript装饰器?,typescript,decorator,Typescript,Decorator,现在有了 有人能提供一个简单的例子来演示实现装饰器的正确方法,并描述可能的有效装饰器签名中的参数的含义吗 declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => voi

现在有了

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

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;
declare-type-ClassDecorator=(target:TFunction)=>TFunction | void;
声明类型PropertyCorator=(目标:对象,propertyKey:string |符号)=>void;
声明类型MethodDecorator=(目标:对象,属性key:string |符号,描述符:TypedPropertyDescriptor)=>TypedPropertyDescriptor | void;
声明类型ParameterDecorator=(target:Function,propertyKey:string | symbol,parameterIndex:number)=>void;
此外,在实现decorator时,是否有任何最佳实践方面的考虑事项应该牢记在心

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

我最终和装饰师们玩了起来,并决定在任何文档出现之前,为任何想利用这一点的人记录我的想法。如果您发现任何错误,请随时编辑此内容

一般要点
  • 在声明类时调用装饰器,而不是在实例化对象时调用装饰器
  • 可以在同一个类/属性/方法/参数上定义多个装饰器
  • 构造函数上不允许使用装饰符
有效的装饰器应为:

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

  • 方法/形式访问器装饰器 实施参数:

    • target
      :类的原型(
      Object
    • propertyKey
      :方法的名称(
      string
      |)
    • descriptor
      :A-如果您不熟悉描述符的键,我建议您阅读
      Object.defineProperty
      (这是第三个参数)中的相关内容
    示例-无参数 使用:

    实施:

    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;
    }
    
    输出:

    方法参数为:[“测试”]

    返回值为:Message--testing

    注:

    • 设置描述符的值时不要使用箭头语法
    • 修改原始描述符比返回新描述符覆盖当前描述符更好。这允许您使用多个装饰器来编辑描述符,而不会覆盖另一个装饰器所做的工作。这样做允许您同时使用
      @enumerable(false)
      @log
      之类的内容(例如:vs)
    • 有用的:可以使用
      TypedPropertyDescriptor
      的type参数来限制装饰器可以放置哪些方法签名()或访问器签名()
    示例-带参数(装饰器工厂) 使用参数时,必须声明一个带有装饰器参数的函数,然后返回一个带有示例签名的函数(不带参数)

    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
      :声明装饰程序的类(
      t函数扩展函数
    :使用元数据api存储类的信息


    房地产装饰师 实施参数:

    • target
      :类的原型(
      Object
    • propertyKey
      :属性的名称(
      string
      |)
    :创建一个
    @serialize(“serializedName”)
    装饰器,并将属性名称添加到要序列化的属性列表中


    参数装饰器 实施参数:

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

    详细例子
    • -方法,获取/设置访问器装饰器示例

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

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

        查看TypeScript手册。

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

        语法: 装饰符是用
        @
        符号声明的,例如
        @metadata
        。TS现在将搜索相应的元数据函数,并将自动为其提供七个参数,这些参数随具体修饰的内容而变化(例如,类或类属性获取不同的参数)

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

        • 类的原型对象
        • propertykey或方法名
        • PropertyDescriptor对象,如下所示
          {writeable:true,enumerable:false,configurable:true,value:ƒ}
        根据decorator的类型,这些参数中的1-3个将传递给decorator函数

        new MyClass().myMethod("testing");
        
        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 {}
        
        class MyClass {
            @serialize
            name: string;
        }
        
        class MyClass {
            myMethod(@myDecorator myParameter: string) {}
        }
        
        // 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 { ... }
        
        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
        
         //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);       
                }
              };
            };
          }
        
          @Prefixer('Mr')
          class Person {
            name = 'MBB';
          
            constructor() {
              console.log('original ctr logic here!');
            }
          }
          
          const pers = new Person();
          
          console.log(pers); //Mr.MBB