Come implementare un decoratore dattiloscritto?


207

TypeScript 1.5 ora ha decoratori .

Qualcuno potrebbe fornire un semplice esempio che dimostra il modo corretto di implementare un decoratore e descrivere cosa significano gli argomenti nelle possibili firme del decoratore valide?

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;

Inoltre, ci sono considerazioni sulle migliori pratiche da tenere a mente durante l'implementazione di un decoratore?


Nota a me stesso :-) se vuoi iniettare un @Injectablein un decoratore,
fai

Vorrei suggerire di dare un'occhiata ai molteplici esempi di questo progetto. Ci sono più decoratori - alcuni sono molto semplici e alcuni potrebbero essere un po 'più difficili da capire: github.com/vlio20/utils-decorators
vlio20

Risposte:


396

Ho finito per giocare con i decoratori e ho deciso di documentare ciò che ho capito per chiunque voglia approfittare di questo prima che venga pubblicata qualsiasi documentazione. Non esitare a modificarlo in caso di errori.

Punti generali

  • I decoratori vengono chiamati quando viene dichiarata la classe, non quando un oggetto viene istanziato.
  • Più decoratori possono essere definiti sulla stessa Classe / Proprietà / Metodo / Parametro.
  • I decoratori non sono ammessi ai costruttori.

Un decoratore valido dovrebbe essere:

  1. Assegnabile a uno dei tipi di Decoratore ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator).
  2. Restituisce un valore (nel caso di decoratori di classe e decoratore di metodi) che è assegnabile al valore decorato.

Riferimento


Metodo / Decoratore di accessori formale

Parametri di attuazione:

  • target: Il prototipo della classe ( Object).
  • propertyKey: Il nome del metodo ( string| symbol).
  • descriptor: A TypedPropertyDescriptor- Se non hai familiarità con le chiavi di un descrittore, consiglierei di leggerlo in questa documentazione su Object.defineProperty(è il terzo parametro).

Esempio: senza argomenti

Uso:

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

Implementazione:

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

Ingresso:

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

Produzione:

I metodi args sono: ["testing"]

Il valore restituito è: Messaggio - test

Appunti:

  • Non utilizzare la sintassi della freccia durante l'impostazione del valore del descrittore. Se lo fai, il contesto di thisnon sarà l'istanza.
  • È meglio modificare il descrittore originale piuttosto che sovrascrivere quello corrente restituendo un nuovo descrittore. Ciò consente di utilizzare più decoratori che modificano il descrittore senza sovrascrivere ciò che ha fatto un altro decoratore. In questo modo puoi utilizzare qualcosa di simile @enumerable(false)e @logallo stesso tempo (Esempio: Bad vs Good )
  • Utile : l'argomento tipo di TypedPropertyDescriptorpuò essere utilizzato per limitare le firme dei metodi ( Esempio di metodo ) o le firme degli accessori ( Esempio di accessori ) su cui è possibile applicare il decoratore.

Esempio: con argomenti (fabbrica di decorazioni)

Quando si utilizzano gli argomenti, è necessario dichiarare una funzione con i parametri del decoratore, quindi restituire una funzione con la firma dell'esempio senza argomenti.

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

Decoratore con metodo statico

Simile a un decoratore di metodi con alcune differenze:

  • Suo target parametro è la funzione di costruzione stessa e non il prototipo.
  • Il descrittore è definito sulla funzione di costruzione e non sul prototipo.

Decoratore di classe

@isTestable
class MyClass {}

Parametro di attuazione:

  • target: La classe in cui viene dichiarato il decoratore ( TFunction extends Function).

Esempio di utilizzo : utilizzo dell'API dei metadati per memorizzare informazioni su una classe.


Decoratore di proprietà

class MyClass {
    @serialize
    name: string;
}

Parametri di attuazione:

  • target: Il prototipo della classe (Object).
  • propertyKey: Il nome della proprietà ( string| symbol).

Esempio di utilizzo : creazione di un @serialize("serializedName")decoratore e aggiunta del nome della proprietà a un elenco di proprietà da serializzare.


Decoratore di parametri

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

Parametri di attuazione:

  • target: Il prototipo della classe ( Function—sembra Functionche non funzioni più. Dovresti usare anyo Objectqui ora per usare il decoratore all'interno di qualsiasi classe. O specificare i tipi di classe a cui vuoi limitarlo)
  • propertyKey: Il nome del metodo ( string| symbol).
  • parameterIndex: L'indice del parametro nell'elenco dei parametri della funzione ( number).

Semplice esempio

Esempi dettagliati


Sai dove trovare un esempio di Parameter Decorator? Ho cercato di implementarne uno senza successo github.com/Microsoft/TypeScript/issues/…
Remo H. Jansen,

1
@OweRReLoaDeD Ho aggiunto un esempio in Decoratore parametri che disconnette semplicemente ciò che è passato al decoratore. Non sono sicuro che sia utile, però. Non riesco a pensare a un buon esempio al momento.
David Sherret,

Cordiali saluti, ho raccolto e ottimizzato queste informazioni su github: github.com/arolson101/typescript-decorators
arolson101 il

--EsperimentalDecorators flag deve essere impostato affinché questo esempio funzioni
Trident D'Gao,

Sono un po 'confuso su ciò che targeto prototype of the classe keysi riferisce a, qualcuno potrebbe elaborare su questo?
Satej S

8

Una cosa importante che non vedo nelle altre risposte:

Fabbrica di decoratori

Se vogliamo personalizzare il modo in cui un decoratore viene applicato a una dichiarazione, possiamo scrivere una fabbrica di decoratori. Una Factory Decorator è semplicemente una funzione che restituisce l'espressione che verrà chiamata dal decoratore in fase di esecuzione.

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

Consulta il capitolo Decoratori del manuale TypeScript .


4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • obiettivo: prototipo della classe nel caso sopra è "Foo"
  • propertyKey: nome del metodo chiamato, nel caso precedente "Boo"
  • descrittore: descrizione dell'oggetto => contiene la proprietà value, che a sua volta è la funzione stessa: function (name) {return 'Hello' + name; }

È possibile implementare qualcosa che registra ogni chiamata alla 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;
  }     
}

1
È un compito difficile farlo compilare con rigide impostazioni del compilatore
PandaWood,

In realtà, questo è sbagliato e non può essere compilato, ci devono essere parentesi graffe direttamente dopo il ritorno {valore: ...}. Questo può essere visto anche da una potenziale fonte del tuo codice - blog.wolksoftware.com/…
PandaWood
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.