Wie implementiere ich einen Typoskript-Dekorator?

207

TypeScript 1.5 hat jetzt Dekoratoren .

Könnte jemand ein einfaches Beispiel liefern, das die richtige Art der Implementierung eines Dekorateurs demonstriert und beschreibt, was die Argumente in den möglichen gültigen Dekorationssignaturen bedeuten?

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;

Gibt es außerdem Best-Practice-Überlegungen, die bei der Implementierung eines Dekorateurs berücksichtigt werden sollten?

David Sherret
quelle
Hinweis für mich :-) Wenn Sie einen @Injectablein einen Dekorateur injizieren möchten , beziehen Sie sich auf
Anand Rockzz
Ich würde vorschlagen, einen Blick auf die zahlreichen Beispiele zu werfen, die dieses Projekt hat. Es gibt mehrere Dekorateure - einige sind sehr einfach und einige sind möglicherweise etwas schwieriger zu verstehen: github.com/vlio20/utils-decorators
vlio20

Antworten:

396

Am Ende habe ich mit Dekorateuren herumgespielt und beschlossen, zu dokumentieren, was ich für jeden herausgefunden habe, der dies nutzen möchte, bevor eine Dokumentation herauskommt. Bitte zögern Sie nicht, dies zu bearbeiten, wenn Sie Fehler sehen.

Allgemeine Punkte

  • Dekoratoren werden aufgerufen, wenn die Klasse deklariert wird - nicht, wenn ein Objekt instanziiert wird.
  • Mehrere Dekorateure können für dieselbe Klasse / Eigenschaft / Methode / Parameter definiert werden.
  • Dekorateure sind auf Konstruktoren nicht erlaubt.

Ein gültiger Dekorateur sollte sein:

  1. Zuweisbar auf einen der Decorator-Typen ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator).
  2. Gibt einen Wert zurück (im Fall von Klassendekoratoren und Methodendekoratoren), der dem dekorierten Wert zugewiesen werden kann.

Referenz


Method / Formal Accessor Decorator

Implementierungsparameter:

  • target: Der Prototyp der Klasse ( Object).
  • propertyKey: Der Name der Methode ( string| symbol).
  • descriptor: A TypedPropertyDescriptor- Wenn Sie mit den Schlüsseln eines Deskriptors nicht vertraut sind, würde ich empfehlen, darüber in dieser Dokumentation zu lesen Object.defineProperty(dies ist der dritte Parameter).

Beispiel - Ohne Argumente

Verwenden:

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

Implementierung:

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

Eingang:

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

Ausgabe:

Die Methodenargumente sind: ["Testen"]

Der Rückgabewert lautet: Nachrichtentest

Anmerkungen:

  • Verwenden Sie keine Pfeilsyntax, wenn Sie den Wert des Deskriptors festlegen. Der Kontext von thiswird nicht der der Instanz sein, wenn Sie dies tun.
  • Es ist besser, den ursprünglichen Deskriptor zu ändern, als den aktuellen zu überschreiben, indem Sie einen neuen Deskriptor zurückgeben. Auf diese Weise können Sie mehrere Dekorateure verwenden, die den Deskriptor bearbeiten, ohne zu überschreiben, was ein anderer Dekorateur getan hat. Auf diese Weise können Sie so etwas wie @enumerable(false)und @loggleichzeitig verwenden (Beispiel: Schlecht gegen Gut )
  • Nützlich : Mit dem Argument type von TypedPropertyDescriptorkönnen Sie einschränken, auf welche Methodensignaturen ( Methodenbeispiel ) oder Accessorsignaturen ( Accessor-Beispiel ) der Dekorator angewendet werden kann.

Beispiel - Mit Argumenten (Decorator Factory)

Wenn Sie Argumente verwenden, müssen Sie eine Funktion mit den Parametern des Dekorateurs deklarieren und dann eine Funktion mit der Signatur des Beispiels ohne Argumente zurückgeben.

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

Ähnlich einem Methodendekorateur mit einigen Unterschieden:

  • Sein targetParameter ist die Konstruktorfunktion selbst und nicht der Prototyp.
  • Der Deskriptor wird in der Konstruktorfunktion und nicht im Prototyp definiert.

Klassendekorateur

@isTestable
class MyClass {}

Implementierungsparameter:

  • target: Die Klasse, für die der Dekorateur deklariert ist ( TFunction extends Function).

Anwendungsbeispiel : Verwenden der Metadaten-API zum Speichern von Informationen zu einer Klasse.


Property Decorator

class MyClass {
    @serialize
    name: string;
}

Implementierungsparameter:

  • target: Der Prototyp der Klasse ( Object).
  • propertyKey: Der Name der Eigenschaft ( string| symbol).

Anwendungsbeispiel : Erstellen eines @serialize("serializedName")Dekorators und Hinzufügen des Eigenschaftsnamens zu einer Liste der zu serialisierenden Eigenschaften.


Parameter Decorator

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

Implementierungsparameter:

  • target: Der Prototyp der Klasse ( Function- es scheint Functionnicht mehr zu funktionieren. Sie sollten anyoder Objecthier jetzt verwenden, um den Dekorator in einer Klasse zu verwenden. Oder geben Sie die Klassentypen an, auf die Sie ihn einschränken möchten.)
  • propertyKey: Der Name der Methode ( string| symbol).
  • parameterIndex: Der Parameterindex in der Liste der Funktionsparameter ( number).

Einfaches Beispiel

Detaillierte Beispiele

David Sherret
quelle
Wissen Sie, wo Sie ein Beispiel für einen Parameterdekorator finden? Ich habe versucht, eine ohne Erfolg zu implementieren github.com/Microsoft/TypeScript/issues/…
Remo H. Jansen
1
@OweRReLoaDeD Ich habe unter Parameter decorator ein Beispiel hinzugefügt, das nur abmeldet, was an den Decorator übergeben wurde. Ich bin mir nicht sicher, ob das hilfreich ist. Ich kann mir im Moment kein gutes Beispiel vorstellen.
David Sherret
Zu Ihrer Information Ich habe diese Informationen auf github gesammelt und optimiert: github.com/arolson101/typescript-decorators
arolson101
--experimentalDecorators Flag muss gesetzt werden, damit dieses Beispiel funktioniert
Trident D'Gao
Ich bin etwas verwirrt darüber, was targetoder was prototype of the classund keybezieht sich darauf. Könnte jemand bitte näher darauf eingehen?
Satej S
8

Eine wichtige Sache sehe ich in den anderen Antworten nicht:

Dekorateur Fabrik

Wenn wir anpassen möchten, wie ein Dekorateur auf eine Deklaration angewendet wird, können wir eine Dekorateurfabrik schreiben. Eine Decorator Factory ist einfach eine Funktion, die den Ausdruck zurückgibt, der vom Decorator zur Laufzeit aufgerufen wird.

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

Lesen Sie das Kapitel TypeScript-Handbuch Dekorateure .

Ondra Žižka
quelle
4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • Ziel: Prototyp der Klasse im obigen Fall ist es "Foo"
  • propertyKey: Name der aufgerufenen Methode, im obigen Fall "Boo"
  • Deskriptor: Beschreibung des Objekts => enthält die Werteigenschaft, die wiederum die Funktion selbst ist: function (name) {return 'Hello' + name; }}

Sie können etwas implementieren, das jeden Aufruf an die Konsole protokolliert:

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;
  }     
}
Erik Lieben
quelle
1
Es ist eine schwierige Aufgabe, dies mit strengen Compiler-Einstellungen zu kompilieren
PandaWood
Tatsächlich ist dies falsch und kann nicht kompiliert werden. Es müssen Klammern direkt nach der Rückgabe {value: ...} geschweift werden. Dies kann sogar aus einer potenziellen Quelle Ihres Codes gesehen werden - blog.wolksoftware.com/…
PandaWood