Jak zaimplementować dekorator maszynopisów?

207

TypeScript 1.5 ma teraz dekoratory .

Czy ktoś mógłby podać prosty przykład pokazujący właściwy sposób implementacji dekoratora i opisać, co oznaczają argumenty w możliwych prawidłowych podpisach dekoratora?

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;

Ponadto, czy są jakieś względy dotyczące najlepszych praktyk, o których należy pamiętać przy wdrażaniu dekoratora?

David Sherret
źródło
Uwaga dla mnie :-) jeśli chcesz wstrzyknąć @Injectabledekoratorowi, zapoznaj się z
Anand Rockzz
Proponuję rzucić okiem na wiele przykładów tego projektu. Istnieje wiele dekoratorów - niektóre są bardzo proste, a niektóre mogą być nieco trudniejsze do zrozumienia: github.com/vlio20/utils-decorators
vlio20

Odpowiedzi:

396

W końcu bawiłem się z dekoratorami i postanowiłem udokumentować to, co wymyśliłem dla każdego, kto chce skorzystać z tego, zanim pojawi się jakakolwiek dokumentacja. Jeśli zauważysz jakieś błędy, edytuj to.

Punkty ogólne

  • Dekoratory są wywoływane, gdy klasa jest zadeklarowana, a nie podczas tworzenia obiektu.
  • W tej samej klasie / właściwości / metodzie / parametrze można zdefiniować wiele dekoratorów.
  • Dekoratorzy nie są dozwoleni na konstruktorach.

Prawidłowym dekoratorem powinien być:

  1. Można przypisać do jednego z typów Dekoratora ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator).
  2. Zwraca wartość (w przypadku dekoratorów klas i dekoratorów metod), którą można przypisać do dekorowanej wartości.

Odniesienie


Method / Formal Accessor Decorator

Parametry realizacji:

  • target: Prototyp klasy ( Object).
  • propertyKey: Nazwa metody ( string| symbol).
  • descriptor: A TypedPropertyDescriptor- Jeśli nie znasz kluczy deskryptora, polecam przeczytać o tym w tej dokumentacji na Object.defineProperty(jest to trzeci parametr).

Przykład - bez argumentów

Posługiwać się:

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

Realizacja:

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

Wejście:

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

Wynik:

Argumenty metody to: [„testowanie”]

Zwracana wartość to: Wiadomość - testowanie

Uwagi:

  • Nie używaj składni strzałek podczas ustawiania wartości deskryptora. Jeśli tak, to nie będzie to kontekst thisinstancji.
  • Lepiej jest zmodyfikować oryginalny deskryptor niż zastąpić obecny, zwracając nowy deskryptor. Pozwala to na użycie wielu dekoratorów, które edytują deskryptor bez nadpisywania tego, co zrobił inny dekorator. Dzięki temu możesz użyć czegoś podobnego @enumerable(false)i @logjednocześnie (przykład: Zły kontra Dobry )
  • Przydatne : Argument typu TypedPropertyDescriptormoże służyć do ograniczenia podpisów metod ( Przykład metody ) lub podpisów modułu dostępu ( Przykład modułu dostępu), w których można umieścić dekorator.

Przykład - z argumentami (fabryka dekoratorów)

Używając argumentów, musisz zadeklarować funkcję z parametrami dekoratora, a następnie zwrócić funkcję z podpisem przykładu bez argumentów.

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

Dekorator metody statycznej

Podobne do dekoratora metod z pewnymi różnicami:

  • Jego targetparametrem jest sama funkcja konstruktora, a nie prototyp.
  • Deskryptor jest zdefiniowany w funkcji konstruktora, a nie w prototypie.

Dekorator klasy

@isTestable
class MyClass {}

Parametr wykonania:

  • target: Klasa, w której dekorator jest zadeklarowany ( TFunction extends Function).

Przykład użycia : użycie interfejsu API metadanych do przechowywania informacji o klasie.


Dekorator nieruchomości

class MyClass {
    @serialize
    name: string;
}

Parametry realizacji:

  • target: Prototyp klasy ( Object).
  • propertyKey: Nazwa właściwości ( string| symbol).

Przykład zastosowania : tworzenie @serialize("serializedName")dekoratora i dodawanie nazwy właściwości do listy właściwości do serializacji.


Dekorator parametrów

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

Parametry realizacji:

  • target: Prototyp klasy ( Function- wydaje się, Functionże już nie działa. Powinieneś użyć anylub Objecttutaj teraz, aby użyć dekoratora w dowolnej klasie. Lub określ typy klas, do których chcesz je ograniczyć)
  • propertyKey: Nazwa metody ( string| symbol).
  • parameterIndex: Indeks parametru na liście parametrów funkcji ( number).

Prosty przykład

Szczegółowy przykład

David Sherret
źródło
Czy wiesz, gdzie znaleźć przykład dekoratora parametrów? Próbowałem wdrożyć jeden bez powodzenia github.com/Microsoft/TypeScript/issues/…
Remo H. Jansen
1
@OweRReLoaDeD Dodałem przykład pod dekoratorem parametrów, który po prostu wylogowuje, co zostało przekazane do dekoratora. Nie jestem jednak pewien, czy to jest pomocne. W tej chwili nie mogę wymyślić dobrego przykładu.
David Sherret
Do Twojej wiadomości zebrałem i poprawiłem te informacje na github: github.com/arolson101/typescript-decorators
arolson101
--experimentalDecorators flaga musi być ustawiona, aby ten przykład działał
Trident D'Gao
Jestem nieco zdezorientowany co do tego targeti prototype of the classczy keyodnosi się do tego, czy ktoś mógłby to rozwinąć?
Satej S
8

Jedną ważną rzeczą, której nie widzę w innych odpowiedziach:

Fabryka dekoratorów

Jeśli chcemy dostosować sposób stosowania dekoratora do deklaracji, możemy napisać fabrykę dekoratorów. Fabryka dekoratorów to po prostu funkcja zwracająca wyrażenie, które będzie wywoływane przez dekoratora w czasie wykonywania.

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

Zobacz rozdział Dekoratory w podręczniku TypeScript .

Ondra Žižka
źródło
4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • cel: prototyp klasy w powyższym przypadku to „Foo”
  • propertyKey: nazwa wywoływanej metody, w powyższym przypadku „Boo”
  • descriptor: description of object => zawiera właściwość value, która z kolei jest samą funkcją: function (name) {return 'Hello' + name; }

Możesz zaimplementować coś, co rejestruje każde wywołanie konsoli:

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
źródło
1
Kompilacja
W rzeczywistości jest to niepoprawne i nie można go skompilować, nawiasy klamrowe muszą znajdować się bezpośrednio po zwróceniu {wartość: ...}. Można to zobaczyć nawet z potencjalnego źródła kodu - blog.wolksoftware.com/…
PandaWood,