Comment mettre en œuvre un tapuscrit décorateur?

dactylographié 1.5 a maintenant décorateurs .

est-ce que quelqu'un pourrait fournir un exemple simple démontrant la bonne façon de mettre en œuvre un décorateur et décrire ce que les arguments dans les signatures valides possibles du décorateur signifient?

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;

en outre, y a-t-il des considérations liées aux pratiques exemplaires qui devraient être gardées à l'esprit lors de la mise en œuvre d'un décorateur?

170
demandé sur David Sherret 2015-04-21 17:55:55

3 réponses

j'ai fini par jouer avec les décorateurs et j'ai décidé de documenter ce que j'ai trouvé pour quiconque veut en profiter avant que toute documentation ne sorte. N'hésitez pas à modifier si vous voyez des erreurs.

Généralités

  • les Décorateurs sont appelés lorsque la classe est déclarée-pas lorsqu'un objet est instancié.
  • plusieurs décorateurs peuvent être définis sur le même Classe/Propriété/Méthode/Paramètre.
  • les Décorateurs ne sont pas autorisés sur les constructeurs.

un décorateur valide devrait être:

  1. Assignable à l'un des types Décorateur ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator ).
  2. retourner une valeur (dans le cas de décorateurs de classe et de décorateurs de méthode) qui peut être assignée à la valeur décorée.

Référence


La Méthode / De L'Formel Accesseur Décorateur

paramètres de mise en Œuvre:

  • target : le prototype de la classe ( Object ).
  • propertyKey : le nom de la méthode ( string | symbol ).
  • descriptor : Un TypedPropertyDescriptor - si vous n'êtes pas familier avec les clés d'un descripteur, je vous recommande de lire à son sujet dans cette documentation sur Object.defineProperty (c'est le troisième paramètre).

Exemple-Sans Arguments

Utiliser:

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

mise en Œuvre:

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

entrée:

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

sortie:

La méthode les arguments sont: ["test"]

la valeur de retour est: Message -- testing

Notes:

  • N'utilisez pas la syntaxe des flèches pour définir la valeur du descripteur. , Le contexte de this ne sera pas de l'instance si vous le faites.
  • il est préférable de modifier le descripteur original que d'écraser le descriptif actuel en retournant un nouveau descripteur. Cela vous permet d'utiliser plusieurs décorateurs que modifier le descripteur sans écraser ce qu'un autre décorateur a fait. Faire ceci vous permet d'utiliser quelque chose comme @enumerable(false) et @log en même temps (exemple: mauvais vs bon )
  • utile : l'argument de type de TypedPropertyDescriptor peut être utilisé pour restreindre quelles signatures de méthode ( exemple de méthode ) ou des signatures d'accesseur ( Accessor Example ) le décorateur peut être mis.

Exemple - Avec Des Arguments (Décorateur D'Usine)

lorsque vous utilisez des arguments, vous devez déclarer une fonction avec les paramètres du décorateur puis retourner une fonction avec la signature de l'exemple sans arguments.

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

Décorateur À Méthode Statique

Semblable à un décorateur de méthode avec quelques différences:

  • son paramètre target est la fonction constructeur elle-même et non le prototype.
  • le descripteur est défini sur la fonction constructeur et non sur le prototype.

Classe Décorateur

@isTestable
class MyClass {}

paramètre de mise en œuvre:

  • target : La classe dans laquelle le Décorateur est déclaré ( TFunction extends Function ).

exemple d'utilisation : utilisation de l'api métadonnées pour stocker des informations sur une classe.


Décorateur Immobilier

class MyClass {
    @serialize
    name: string;
}

paramètres de mise en Œuvre:

  • target : le prototype de la classe ( Object ).
  • propertyKey : le nom de la propriété ( string | symbol ).

exemple d'utilisation : création d'un @serialize("serializedName") décorateur et ajout du nom de propriété à une liste de propriétés à sérialiser.


"1519410920 Paramètre" Décorateur
class MyClass {
    myMethod(@myDecorator myParameter: string) {}
}

paramètres de mise en Œuvre:

  • target : le prototype du classe ( Function - il semble que Function ne fonctionne plus. Vous devez utiliser any ou Object ici maintenant, afin d'utiliser le décorateur intérieur d'une même catégorie. Ou spécifiez le(S) Type (S) de classe que vous voulez restreindre à)
  • propertyKey : le nom de la méthode ( string | symbol ).
  • parameterIndex : l'index du paramètre dans la liste des paramètres de la fonction ( number ).

exemple Simple

exemple(s) détaillé (s)

343
répondu David Sherret 2018-02-24 18:17:37

une chose importante que je ne vois pas dans les autres réponses:

Décorateur d'usine

Si nous voulons personnaliser la façon dont un décorateur est appliqué à une déclaration, nous pouvons écrire un décorateur d'usine. Une usine de Décorateur est simplement une fonction qui renvoie l'expression qui sera appelée par le décorateur à l'exécution.

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

Vérifier le fichier d'enregistrement manuel les Décorateurs chapitre .

8
répondu Ondra Žižka 2016-11-02 03:34:08
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • cible: prototype de la classe dans le cas ci-dessus c'est "Foo"
  • propertyKey: nom de la méthode appelée, dans le cas ci-dessus "Boo"
  • descripteur: description de l'objet = > contient la propriété valeur, qui à son tour est la fonction elle - même: fonction(Nom) { retour 'Hello' + nom;}

Vous pourriez mettre en place quelque chose qui se connecte à chaque appel de la 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;
  }     
}
4
répondu Erik Lieben 2015-04-23 13:42:32