簡體   English   中英

如何實現打字稿裝飾器?

[英]How to implement a typescript decorator?

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;

此外,在實現裝飾器時是否應牢記任何最佳實踐注意事項?

我最終玩弄了裝飾器,並決定在任何文檔出現之前為任何想要利用它的人記錄我的想法。 如果您發現任何錯誤,請隨時編輯。

一般要點

  • 裝飾器在聲明類時調用——而不是在實例化對象時調用。
  • 可以在同一個類/屬性/方法/參數上定義多個裝飾器。
  • 構造函數上不允許使用裝飾器。

一個有效的裝飾器應該是:

  1. 可分配給裝飾器類型之一( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator )。
  2. 返回一個可分配給裝飾值的值(在類裝飾器和方法裝飾器的情況下)。

參考


方法/正式訪問器裝飾器

實現參數:

  • target :類的原型( Object )。
  • propertyKey :方法的名稱( string | symbol )。
  • descriptor : A 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");

輸出:

方法參數是:[“測試”]

返回值為:Message——測試

筆記:

  • 設置描述符的值時不要使用箭頭語法。 如果您這樣做,則this上下文將不是實例的上下文。
  • 修改原始描述符比通過返回新描述符覆蓋當前描述符更好。 這允許您使用多個裝飾器來編輯描述符,而不會覆蓋其他裝飾器所做的工作。 這樣做允許您同時使用@enumerable(false)@log類的東西(例如: Bad vs Good
  • 有用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;
    };
}

靜態方法裝飾器

類似於方法裝飾器,但有一些不同:

  • 它的target參數是構造函數本身而不是原型。
  • 描述符是在構造函數上定義的,而不是在原型上定義的。

類裝飾器

@isTestable
class MyClass {}

實現參數:

  • target :裝飾器聲明的類( TFunction extends Function )。

使用示例:使用元數據 api 存儲類的信息。


物業裝飾師

class MyClass {
    @serialize
    name: string;
}

實現參數:

  • target :類的原型( Object )。
  • propertyKeypropertyKey的名稱( string | symbol )。

使用示例:創建@serialize("serializedName")裝飾器並將屬性名稱添加到要序列化的屬性列表中。


參數裝飾器

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

實現參數:

  • target :類的原型( Function似乎Function不再起作用。你現在應該在這里使用anyObject以便在任何類中使用裝飾器。或者指定你想要限制它的類類型到)
  • propertyKey :方法的名稱( string | symbol )。
  • parameterIndex :函數參數列表中parameterIndex的索引( number )。

簡單的例子

詳細示例

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

裝飾廠

如果我們想自定義裝飾器如何應用於聲明,我們可以編寫一個裝飾器工廠。 裝飾器工廠只是一個返回表達式的函數,該表達式將在運行時被裝飾器調用。

// 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 手冊裝飾器章節

class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • 目標:上述情況下類的原型是“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;
  }     
}

TS 裝飾器:

TS 裝飾器允許在類上添加額外的功能。 在創建該類的任何實例之前,該類在聲明時由裝飾器更改。

句法:

裝飾@metadata @符號聲明,例如@metadata TS 現在將搜索相應的元數據函數,並自動為它提供 sevaral 參數,這些參數因具體裝飾的內容而異(例如,類或類屬性獲得不同的參數)

這些參數在裝飾器函數中提供:

  • 類的原型對象
  • 屬性鍵或方法名稱
  • PropertyDescriptor 對象,看起來像這樣{writable: true, enumerable: false, configurable: true, value: ƒ}

根據裝飾器的類型,將這些參數的 1-3 傳遞給裝飾器函數。

裝飾器的類型:

以下裝飾器可以應用於類,TS 將按以下順序評估它們(以下總和來自 TS 文檔):

  1. 每個實例成員都應用參數裝飾器,然后是方法、訪問器或屬性裝飾器。
  2. 每個靜態成員都應用參數裝飾器,然后是方法、訪問器或屬性裝飾器。
  3. 參數裝飾器應用於構造函數。
  4. 類裝飾器應用於類

更好地理解它們的最好方法是通過示例。 請注意,這些示例確實需要對 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
 

裝飾器和裝飾器工廠:

裝飾器可以通過裝飾器函數或裝飾器工廠函數來聲明。 語法上有區別,最好通過一個例子來解釋:

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

您還可以在打字稿中decorate/enhance原始構造函數的新功能(我使用了 3.9.7)。 下面的代碼片段包裝了原始構造函數以添加 name 屬性的前綴。 當類被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);       
        }
      };
    };
  }

當類被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