Typescript: Décorateurs

Créé le 7 mars 2015  ·  139Commentaires  ·  Source: microsoft/TypeScript

Proposition ES7

La proposition ES7 pour les décorateurs se trouve ici : https://github.com/wycats/javascript-decorators
La proposition ES7 sert de base à cette proposition. Vous trouverez ci-dessous des notes sur la façon dont le système de types

Cibles du décorateur :

Constructeur de classe

@F("color")
<strong i="12">@G</strong>
class Foo {
}

dessucres à :

var Foo = (function () {
    function Foo() {
    }
    Foo = __decorate([F("color"), G], Foo);
    return Foo;
})();

Méthodes

class Foo {
  @F(color)
  <strong i="19">@G</strong>
  bar() { }
}

dessucres à :

var Foo = (function () {
    function Foo() {
    }
    Foo.prototype.bar = function () {
    };
    Object.defineProperty(Foo.prototype, "bar", __decorate([F(color), G], Foo.prototype, "bar", Object.getOwnPropertyDescriptor(Foo.prototype, "bar")));
    return Foo;
})();

Méthode statique

class Foo {
    @F("color")
    <strong i="26">@G</strong>
    static sMethod() {}
}

dessucres à :

var Foo = (function () {
    function Foo() {
    }
    Foo.sMethod = function () {
    };
    Object.defineProperty(Foo, "sMethod", __decorate([F("color"), G], Foo, "sMethod", Object.getOwnPropertyDescriptor(Foo, "sMethod")));
    return Foo;
})();

Propriétés

class Foo {
    @F("color")
    <strong i="33">@G</strong>
    prop: number;
}

dessucres à :

var Foo = (function () {
    function Foo() {
    }
    __decorate([F("color"), G], Foo.prototype, "prop");
    return Foo;
})();

Paramètre formel Méthode/Accesseur

class Foo {
    method(<strong i="40">@G</strong> a, @F("color") b) {}
}

dessucres à :

var Foo = (function () {
    function Foo() {
    }
    Foo.prototype.method = function (a, b) {
    };
    __decorate([G], Foo.prototype, "method", 0);
    __decorate([F("color")], Foo.prototype, "method", 1);
    return Foo;
})();

Où le __decorate est défini comme :

var __decorate = this.__decorate || function (decorators, target, key, value) {
    var kind = typeof (arguments.length == 2 ? value = target : value);
    for (var i = decorators.length - 1; i >= 0; --i) {
        var decorator = decorators[i];
        switch (kind) {
            case "function": value = decorator(value) || value; break;
            case "number": decorator(target, key, value); break;
            case "undefined": decorator(target, key); break;
            case "object": value = decorator(target, key, value) || value; break;
        }
    }
    return value;
};

Signatures du décorateur :

Un décorateur valide doit être :

  1. Affectable à l'un des types de décorateur (ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator) comme décrit ci-dessous.
  2. Renvoie une valeur (dans le cas des décorateurs de classe et du décorateur de méthode) pouvant être affectée à la valeur décorée.
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;

Remarques:

  • Décorer une déclaration de fonction n'est pas autorisé car cela bloquera le hissage de la fonction au sommet de la portée, ce qui constitue un changement important dans la sémantique.
  • Les expressions de fonction de décoration et les fonctions de flèche ne sont pas prises en charge. Le même effet peut être obtenu en appliquant la fonction de décorateur comme var x = dec(function () { });
  • La décoration des paramètres formels de la fonction ne fait actuellement pas partie de la proposition ES7.
  • Les décorateurs ne sont pas autorisés lorsqu'ils ciblent ES3
Committed ES Next Fixed Suggestion

Commentaire le plus utile

La décoration de fonction est bien sûr nécessaire.
Existe-t-il également des projets de décoration d'autres objets dans le code ?

Tous les 139 commentaires

Excusez-moi d'après ce que je comprends de la spécification, nous ne pourrons pas faire :

<strong i="6">@F</strong>
function test() {
}

Ai-je raison ?

Comment la sérialisation de type fonctionne-t-elle avec les arguments de repos ?

@F()  
class Foo {  
    constructor(...args: string[]) {  
    }  
}  

function F(<strong i="6">@paramterTypes</strong> types?: Function[]) {  
    return function (target) {  
        target.paramterTypes = types; // ???  
    }  
}

L'utilisation de décorateurs semble assez simple, mais j'ai trouvé les sections sur leur déclaration déroutantes. C.4 dit que les décorateurs doivent être annotés avec @decorator , mais aucun des exemples ne montre réellement que cela se produit.

Les usines de décorateurs sont-elles destinées à être des classes qui implémentent les interfaces trouvées dans B ?

Quelle est la règle pour affiner l'interprétation de CoverMemberExpressionSquareBracketsAndComputedPropertyName ?

J'ai remarqué que de nombreuses saisies ont Function | Object à divers moments, mais celles-ci dégénéreront en Objet au moment de la vérification du type. Quelle est la raison d'avoir Function là-bas ?

Je ne suis pas fou des termes DecoratorFunction vs DecoratorFactory. Je préfère de loin suivre la nomenclature des générateurs, qui a Generator et GeneratorFunction. Avec ce schéma, nous renommerions DecoratorFunction en Decorator et DecoratorFactory en DecoratorFunction.

Pour les exportations décorées, à quoi sert [lookahead ≠ @] ? HoistableDeclaration et ClassDeclaration peuvent-ils réellement commencer par un @ ?

C'est un doublon de #1557

Ce n'est pas vraiment une dupe, car le n° 1557 était pour un design différent. Ce problème concerne la conception des décorateurs en cours de mise en œuvre.

Mon erreur.

Pour le décorateur sur l'expression de fonction, ne pourrions-nous pas faire quelque chose comme :

@F("color") <strong i="6">@G</strong> 
function myFunc() {
   doSomething();
}

transformé en :

var _t = function() {
   doSomething();
}
_t = F("color")(_t = G(_t) || _t) || _t;  

function myFunc() {
  return _t.apply(this, arguments)
}

C'est un peu embêtant d'avoir à rectifier toutes les fonctions comme :

const myFunc = function () {}

Vous perdez le levage, et function.name

Implémentation ajoutée dans PR #2399

Mises à jour : proposition mise à jour et ajout d'un lien vers les décorateurs JavaScript @wycats ES7.

attristé que cela soit devenu une seule chose de classe ...
Et les décorateurs d'ambiance sont-ils sortis du champ ?

@fdecampredon avec ta proposition de fonctions, on dirait que tu perds encore le levage.

@JsonFreeman pourquoi ? si vous insérez _t en haut du fichier ?

import something from 'something';

myFunc(something.something());

@F("color") <strong i="8">@G</strong> 
function myFunc() {
  doSomething()
}
import something from 'something';

var _t = function() {
   doSomething();
}
_t = F("color")(_t = G(_t) || _t) || _t;  

myFunc(something.something());

function myFunc() {
  return _t.apply(this, arguments)
}

De plus, même si ma proposition présente de nombreux problèmes, j'aimerais sérieusement pouvoir utiliser des décorateurs sur la fonction (même si je dois utiliser une fonction affectée à une variable) et perdre le levage.
Un cas comme celui-ci semble être un très bon cas d'utilisation de décorateur pour la fonction et la classe (en particulier couplé avec les décorateurs ambiants s'ils finissent toujours par être dans le champ d'application)

@fdecampredon cela ne fonctionne pas pour le cas général, car les décorateurs sont eux-mêmes des expressions. par exemple

myFunc();  // assumes function declaration is hoisted

var dec = (t) => t; // defininig a decorator

<strong i="7">@dec</strong>
function myFunc() {}

si vous hissez la déclaration de fonction et l'application du décorateur, vous cassez le décorateur. si vous ne soulevez que la déclaration de fonction, mais pas l'application de décoration, vous pouvez voir la fonction dans un état non décoré. pas de solutions intéressantes ici.

c'est le même problème qu'avec la clause d'extension de déclaration de classe, qui dans ES6 est une expression. le résultat ne soulevait pas la déclaration de classe, juste le symbole (semblable à la déclaration var, mais pas aux déclarations de fonction)

Oups n'y avait pas pensé merci @mhegazy.
Cependant pourquoi la partie fonction a complètement abandonné la proposition originale de @jonathandturner avait la règle :

decorated function declarations cannot be hoisted to the containing scope

Perdre le levage est certainement un inconvénient, mais je trouve dommage de le transformer en une fonctionnalité de classe uniquement alors qu'il aurait un cas d'utilisation pour une autre construction.

Voyons ce que l'ensemble de contraintes souhaité semble impliquer :

  • la fonction décorée doit être hissée
  • la fonction doit être décorée dès qu'elle est disponible - par conséquent, l'application du décorateur doit être hissée
  • le décorateur doit être défini avant d'être appliqué - donc la définition du décorateur elle-même (ou toutes les entités référencées par l'expression du décorateur) doit être hissée
  • l'expression du décorateur est évaluée à l'endroit où elle est appliquée lexicalement - donc l'application ne peut pas être hissée

La seule résolution que je peux voir pour cela est la suivante : pour les décorateurs de fonctions, nous n'autorisons que quelque chose de la forme @identifier . Nous n'autorisons pas une expression de gauche. De plus, l'identifiant doit être une référence directe à une déclaration de fonction (y compris une fonction décorée). Toutes les décorations de fonction qui ont lieu dans la portée doivent être émises en haut de la portée, dans l'ordre dans lequel elles ont été appliquées.

Le problème de briser les règles de levage, c'est que c'est surprenant. Si vous écrivez du javascript pendant un certain temps, vous vous attendez à ce que les déclarations de fonctions soient disponibles avant qu'elles ne soient déclarées lexicalement ; maintenant, en ajoutant un marqueur syntaxique apparemment simple (le décorateur), cette nature fondamentale de la déclaration de fonction est modifiée.

Cela dit, la proposition ES7 n'en est qu'à ses débuts, je m'attends donc à ce qu'elle évolue et s'étende ; il est donc concevable que la proposition finale inclue des fonctions sous une forme ou une autre.

Je pense qu'ils devraient être hissés. Je dis que les seules décorations que nous permettons sur les fonctions sont des décorations qui sont définitivement elles-mêmes hissées. À savoir des identifiants qui référencent des déclarations de fonctions.

Mais il y a un autre problème ici. Il est en effet impossible de hisser simultanément toutes les fonctions décorées et de s'assurer qu'elles ne sont pas observées dans leur état non décoré. Le problème peut être vu avec un cycle de décorateur.

<strong i="7">@dec1</strong>
function dec2(target: Function) {
   // Do stuff
}

<strong i="8">@dec2</strong>
function dec1(target: Function) {
   // Do stuff
}

Même si les deux fonctions sont hissées, laquelle est décorée en premier ? Si dec2 est décoré en premier, alors dec1 ne sera pas lui-même décoré au moment où il sera appliqué à dec2.

Nous aurions donc à choisir entre les éléments suivants :

  • Les fonctions ne sont pas hissées
  • Les fonctions non décorées sont hissées, mais les applications de décoration ne sont pas hissées avec elles.

Bien que je n'aime ni l'un ni l'autre, je ne pense même pas que quoi que ce soit d'autre soit possible.

C'est la proposition de JS. donc le moteur ne saurait pas si l'expression fait référence à une déclaration de fonction ou non, avec une analyse statique nous pourrions cependant le dire. considère ceci:

<strong i="6">@dec1</strong>
function dec2(target: Function) {
   // Do stuff
}

dec2 = undefined;

<strong i="7">@dec2</strong>
function dec1(target: Function) {
   // Do stuff
}

Pouah! Javascript ne se simplifie plus. :-)

Il serait plus simple de l'activer uniquement sur la fonction _expressions_ :

const foo = <strong i="6">@decorator</strong> () => {
    // ...   
}
const bar = <strong i="7">@decorator</strong> function() {
    // ...
}

Les expressions de fonction ou les lambda ne sont pas hissées.

Est-il possible d'avoir des paramètres comme celui-ci dans un décorateur en tapuscrit 1.5.0-alpha ?

@dec1({key1: value1, key2, value2})
function dec2(target: Function) {
   // Do stuff
}

Ok, peu importe, créez simplement une usine qui prend les paramètres et renvoie la fonction de décorateur réelle.

Par exemple, un décorateur de classe avec un paramètre de chaîne :

function decoratorWithString(param: string) { // Decorator factory
    return function(target) { // Actual decorator
        // Do stuff with target and string parameter
    }
}

// Usage
@decoratorWithString('foobar')
class Foo {

}

les salutations.

J'essaie de comprendre comment écrire un décorateur qui récupérera les types déclarés dans le constructeur.

voici un code qui illustre ce que j'essaie de faire, mais il est câblé. j'en ai besoin pour répondre à la déclaration du constructeur en classe D

class A {
  public message = "identity: class A";
}

class B {
  public message = "identity: class B";
}

<strong i="8">@decoTest</strong>
class D {
  static metadata:Array<Function> = [];
  constructor(a: A, b: B) {
  }
}
describe("decorators", function() {
  it("should inject constructor types", function() {
    var d = new D(new A(), new B());
    expect(D.metadata.length).toBe(2);
  });
});


function decoTest<T>(target: T, ...rest) {
  target["metadata"].push(A, B); // how do i get this based on constructor ???
  return target;
}

Je crains que les informations de type ne soient hors de portée de la fonction de décoration.
Vous pouvez utiliser ParameterDecorator sur chaque paramètre pour ajouter ce type d'informations.

Le typage ou l'implémentation de ParameterDecorator n'est pas tout à fait correct, d'après les typages, la cible est une fonction, mais lors de l'utilisation de typescript, c'est l'objet prototype.
Je dois caster le paramètre cible afin d'obtenir le bon type :

function MyParameterDecorator (_target: Function, methodName: string, index: number) {
    const target = <InterfaceForMyUseCase><anyt>_target;
    // do stuff
}

À la place de:

function MyParameterDecorator (target: InterfaceForMyUseCase, methodName: string, index: number) {
    // do stuff
}

wow - règle des décorateurs de paramètres !!!!!!!!!!!!!!! voir ce dépôt

voici le résultat de l'exécution des tests :

LOG: 'injectMe:'
LOG: '  type: class A'
LOG: '  type: class B'
LOG: '  some key'

et le code :

module ParameterDecorators {
  class A {
    static typeName:string = "type: class A";
    public instanceTypeName = "instance: class A";
  }

  class B {
    static typeName:string = "type: class B";
    public instanceTypeName = "instance: class B";
  }

  @injectTest(A, B, "some key")
  class C {
    static injectMe: Array<any> = [];
    constructor(a: A, b: B) {
    }
  }

  function injectTest(...rest) {
    return function(target): void {
      target["injectMe"] = rest;
    }
  }

  describe("decorators", function() {
    it("should inject dependency-injection keys", function() {
      var c = new C(new A(), new B());
      console.log("injectMe:");
      for (let parm of C.injectMe) {
        if (typeof(parm) === "function") {
          console.log("\t" + parm["typeName"]);
        } else {
          console.log("\t" + parm)
        }
      }
    });
  });
}

J'ai créé un wrapper autour d'express (mais n'importe quel framework Web peut être pris en charge, une interface d'adaptateur est définie) avec une API basée sur un décorateur : https://github.com/cybrown/web-decorators

J'utilise ClassDecorator, ParameterDecorator et MethodDecorator.

@cybrown Je viens de mettre à jour la signature pour ParameterDecorator dans #2635, qui est maintenant en master.

@rbuckton Merci, je mettrai à jour cela demain dans mon projet.

Est-il possible d'avoir le nom du paramètre dans un ParameterDecorator ?
Cela peut être utile car je pense que le nom du paramètre peut être la moitié du temps le premier argument d'un ParameterDecorator.
De plus, cela pourrait être une solution à la modification des noms de paramètres par les minificateurs.

on m'a demandé d'accomplir ceci :

@inject(A, B, "some key")
  class C {
    static injectMe: Array<any> = [];
    constructor(a: A, b: B) {
    }
  }

  function inject(...rest) {
    return function(target): void {
      target["inject"] = rest;
    }
  }

mais sans spécifier les clés dans le décorateur d'injection, mais à la place, les décorateurs récupèrent automatiquement les fonctions de classe du constructeur avec quelque chose du genre :

inject(<strong i="9">@parameterTypes</strong> types:Function[]){ ... }

je cherche comment faire ce que @jonathandturner décrit ici

@cmichaelgraham Oui, avoir des informations sur le type d'exécution (rtti comme décrit dans ce qui était AtScript) serait génial pour ce genre d'utilisation, même dans une fonctionnalité complètement séparée telle que l'introspection.

@cmichaelgraham Nous travaillons sur une proposition pour ES7 pour ajouter une API de métadonnées qui fonctionnera avec les décorateurs. #2589 ajoute un support expérimental pour cette proposition, mais nécessite la présence d'un polyfill séparé pour lire les métadonnées.

@rbuckton @cmichaelgraham Il y avait à l'origine une conception pour un décorateur TypeScript spécial qui dirait au compilateur d'injecter des types dans le décorateur, en fonction de la cible à décorer. Je pense que c'était @parameterTypes Ai-je raison de penser que cette fonctionnalité est supprimée en faveur de l'API de métadonnées plus générale ? Si oui, je soutiendrais cela.

Pouvez-vous parler un peu du statut de cela par rapport à TypeScript. Est-ce prévu pour la sortie de la 1.5 ? Si oui, à quoi ressembleront les options du compilateur ? Une chose qui serait utile serait que le compilateur génère automatiquement les métadonnées de type pour les signatures de constructeur uniquement.

Les décorateurs 1.5-alpha .

Quant à la prise en charge des métadonnées. Le design a changé depuis la proposition originale de @paramtypes . la nouvelle conception utilise la proposition Reflect.metada. Voir #2589 pour plus de détails sur son fonctionnement. aussi @rbuckton a un pollyfill pour consommer les métadonnées ici : https://github.com/rbuckton/reflectDecorators

@mhegazy je le sais. Voir ici : http://blog.durandal.io/2015/04/09/aurelia-update-with-decorators-ie9-and-more/ :smile:

Je connais également la proposition de métadonnées. J'ai fourni des commentaires à ce sujet. Je voulais juste savoir si l'idée originale @parameterTypes était supprimée.

merci @EisenbergEffect pour le partage. :+1:

Oui. le seul problème avec @paramtypes est qu'il fait émettre dirigé par le système de types, et nécessite des informations globales sur le programme. Cela rompt les scénarios de transpilation à module unique (voir #2499). L'autre option était de le mettre sur des sites d'appels, ce qui ajoute beaucoup de travail aux utilisateurs décorateurs plutôt qu'aux auteurs. Nous sommes donc retournés à la planche à dessin et avons plutôt opté pour l'approche Reflect.metadata.

si vous avez également consulté les versions antérieures de la proposition, nous avons dû supprimer les décorateurs ambiants/de conception pour la même raison.

Merci de clarifier. Tout cela a du sens. Avez-vous une idée si l'approche basée sur Reflect atterrira dans la 1.5 ?

Oui. il est actuellement en master. il devrait être disponible dans la prochaine version. c'est actuellement une fonctionnalité opt-in utilisant le drapeau expérimental --emitDecoratorMetadata ; et il ajoute uniquement des métadonnées aux entités décorées.

"il n'ajoute que des métadonnées aux entités décorées" Pouvez-vous développer cette idée ? Un décorateur spécial est-il impliqué? Ou l'ajoute-t-il à quoi que ce soit avec n'importe quel décorateur? En d'autres termes, si un développeur utilise le décorateur inject Aurelia, cela déclenchera-t-il le compilateur pour générer les métadonnées ?

si un développeur utilise le décorateur d'injection d'Aurelia, cela déclenchera-t-il le compilateur pour générer les métadonnées ?

Oui.

ce que je voulais dire c'est :

<strong i="9">@inject</strong>
class Foo {
    constructor(a: number, b: string) {}
}

class Bar {
    constructor(a: number, b: string) {}
}

avec --emitDecoratorMetadata le compilateur émettra des métadonnées de type (c'est-à-dire appel à reflect.metadata('desing:paramtypes', [Number, String]) ) pour Foo mais _pas_ Bar .

Cela fonctionnera pour nous. Nous préparerons le support de l'API Reflect.metadata pour notre prochaine version, très probablement la semaine prochaine. Merci pour la clarification!

alors qu'est-ce qui est émis pour cela?

``` language=javascript
@injecter
classe Foo {
constructeur (a : A, b : B) {}
}

classe Bar {
constructeur (a : nombre, b : B) {}
}

would it be (for Foo):

``` javascript
reflect.metadata('desing:paramtypes', [A, B])

@cmichaelgraham Oui, c'est à peu près ce qui est généré.

Fou cool !!!!

en utilisant gulp-typescript pour construire ce référentiel - (beau travail @ivogabe)

Commande gulpfile build-ts (notez l'option emitDecoratorMetadata ) :

gulp.task('build-ts', function () {
    var tsResult = gulp.src([
        './views/*.ts',
        './typings/**/*.d.ts',
        './*.ts'
        ],
        {base: "."})
    .pipe(ts({
         typescript: require('typescript'),
         declarationFiles: false,
         noExternalResolve: true,
         target: "es5",
         module: "amd",
         emitDecoratorMetadata: true
    }));

    return merge([
        tsResult.dts.pipe(gulp.dest('.')),
        tsResult.js.pipe(gulp.dest('.'))
    ]);
});

tourne app.ts

import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
import 'bootstrap';
import 'bootstrap/css/bootstrap.css!';

@inject(Router)
export class App {
  public router;
  constructor(router:Router) {
    this.router = router;
    this.router.configure(config => {
      config.title = 'Aurelia';
      config.map([
        { route: ['','welcome'],  moduleId: './welcome',      nav: true, title:'Welcome' },
        { route: 'flickr',        moduleId: './flickr',       nav: true },
        { route: 'child-router',  moduleId: './child-router', nav: true, title:'Child Router' }
      ]);
    });
  }
}

en app.js

var __decorate = this.__decorate || (typeof Reflect === "object" && Reflect.decorate) || function (decorators, target, key, desc) {
    switch (arguments.length) {
        case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target);
        case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0);
        case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc);
    }
};
var __metadata = this.__metadata || (typeof Reflect === "object" && Reflect.metadata) || function () { };
define(["require", "exports", 'aurelia-framework', 'aurelia-router', 'bootstrap', 'bootstrap/css/bootstrap.css!'], function (require, exports, aurelia_framework_1, aurelia_router_1, , ) {
    var App = (function () {
        function App(router) {
            this.router = router;
            this.router.configure(function (config) {
                config.title = 'Aurelia';
                config.map([
                    { route: ['', 'welcome'], moduleId: './welcome', nav: true, title: 'Welcome' },
                    { route: 'flickr', moduleId: './flickr', nav: true },
                    { route: 'child-router', moduleId: './child-router', nav: true, title: 'Child Router' }
                ]);
            });
        }
        App = __decorate([
            aurelia_framework_1.inject(aurelia_router_1.Router), 
            __metadata('design:paramtypes', [aurelia_router_1.Router])
        ], App);
        return App;
    })();
    exports.App = App;
});

les métadonnées de type émises sur les paramètres du constructeur sont particulièrement intéressantes :

        App = __decorate([
            aurelia_framework_1.inject(aurelia_router_1.Router), 
            __metadata('design:paramtypes', [aurelia_router_1.Router])
        ], App);

donc en théorie, vous pouvez modifier la fonction de décorateur d'injection pour injecter les classes appropriées sans les spécifier dans l'injection, mais en spécifiant à la place les types dans le constructeur :)

@cmichaelgraham Nous pouvons facilement activer cela aujourd'hui sans modifier le cadre. La bibliothèque DI d'Aurelia a un crochet container.addParameterInfoLocator . Vous lui passez une fonction qui peut prendre un type et renvoyer ses dépendances. Pour notre prochaine version (la semaine prochaine), nous pouvons cependant l'ajouter à la configuration de base afin qu'il soit facile pour les développeurs TypeScript de l'activer. Je l'aurais mis dans la version de cette semaine, mais je n'avais pas encore réalisé que cela avait changé.

@EisenbergEffect génial !! :+1:

J'ai utilisé des annotations pour marquer les propriétés d'un objet comme observables, ce qui transforme une primitive en un objet observable (pour ceux que cela intéresse, https://github.com/mweststrate/MOBservable/commit/8cc7fc0e20c000db660037c8b5c9d944fe4155d9).

Cependant, en particulier pour les propriétés, il semblait un peu anormal que les annotations soient appliquées au prototype, et non au constructeur d'une classe elle-même, cela signifie qu'il est difficile d'obtenir la valeur initiale, ou même le this . La solution consistait à créer une propriété et, dans son getter/setter, à exécuter la logique réelle que l'annotation était censée faire, car le this et la valeur sont alors disponibles.

En plus de cela, fonctionnalité géniale!

En utilisant l'API Metadata Reflection, j'ai pu effectuer une simple injection de dépendance de propriétés.
Vous pouvez vérifier les modifications requises ici https://github.com/matjaz/property-DI/commit/2b4835e100b72d954b57d0e656ea524539ac17eb.

Le décorateur __metadata dans le code généré ne devrait-il pas être invoqué en premier lieu par les décorateurs ? Je voulais créer un simple deocrator en utilisant les métadonnées de la classe, mais __metadata('design:paramtypes', [TYPES....]) est invoqué en dernier, donc je ne peux pas obtenir ces données de Reflect

@ufon pouvez-vous partager le code que vous regardez ?

Bien sûr, ici https://gist.github.com/ufon/5a2fa2481ac412117532

ÉDITER:
mon mauvais, il y a une autre erreur dans mon code, je ne peux pas obtenir de types même après l'initialisation de la classe

@ufon , je ne suis pas sûr de voir le problème alors. __metadata est le dernier élément de la liste des décorateurs, cela signifie qu'il est le premier à s'exécuter, et certainement avant qu'inject ne s'exécute.

    House = __decorate([
        inject_1.inject, 
        __metadata('design:paramtypes', [Floor, String])
    ], House);

__decorate definition utilise le droit de réduction pour exécuter les décorateurs dans l'ordre inverse.

decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target);

C'est pour correspondre à la spécification, que les décorateurs (en tant qu'expressions) sont évalués dans l'ordre de déclaration, mais exécutés dans l'ordre inverse, permettant aux décorateurs externes de consommer les résultats des décorateurs internes.

Je vois, désolé de vous ennuyer :) mon mauvais, il y a une autre erreur dans mon code, je ne peux pas obtenir de types même après l'initialisation de la classe

comment les interrogez-vous ? et avez-vous un pollyfill pour Reflect.metadata ?

ajouté à l'essentiel...
Reflect.getMetadataKeys(House);
cela se traduit par un tableau vide ..

Oui, j'ai besoin du package 'reflect-metadata'

ressemble au polyfill qui le perd, @rbuckton pouvez-vous jeter un œil.

Je pense que j'ai compris...
les exigences sont déplacées après le code d'aide du script dactylographié

var __metadata = this.__metadata || (typeof Reflect === "object" && Reflect.metadata) || function () { };
require('reflect-metadata');

ainsi, dans le temps, var __metadata est en cours d'initialisation, polyfill n'est pas encore chargé

mais je ne peux pas obtenir ces exigences avant le code généré

EDIT : mise à jour de l'essentiel.. lorsqu'un seul module est compilé, il n'y a aucun moyen de charger polyfill avant que __metadata ne soit résolu, car le code généré par typescript est toujours déplacé vers le haut du fichier

ooh.. c'est un problème plus important. problème enregistré #2811 pour le suivre

Jusque-là, vous avez besoin de reflect-metadata dans un autre module (vous avez besoin de deux fichiers).
voir https://github.com/matjaz/property-DI/

Il semble y avoir une incohérence d'invocation qui rend le développement de décorateurs avec des paramètres variables/facultatifs assez difficile à faire. Par exemple:

<strong i="6">@F</strong>
prop: number;

@F()
prop: number;

Dans le premier, F est invoqué avec les paramètres (target, propertyName, propertyDescriptor) tandis que dans le second, F est invoqué avec les paramètres () et nécessite que vous ayez une fonction interne renvoyée qui à son tour est invoquée avec (target, propertyName et propertyDescriptor).

Mon _hypothèse_ aurait été que @F et @F() seraient équivalents et que vous ne pouvez donc effectuer l'un ou l'autre appel que si le décorateur prend en charge 0 ou plusieurs paramètres.

Ceci est particulièrement important dans le cas :

<strong i="13">@F</strong>  // Performs some default behaviour.
prop: number;

@F({ option: true }) // Performs some configured behaviour.
prop: number;

Cela peut être contourné en appelant simplement @F() au lieu de @F, mais il est facile de faire cette erreur et d'être ensuite confus lorsque votre décorateur ne fonctionne pas, même si, à première vue, il devrait le faire.

Dans ce cas, je voudrais faire ce qui suit :

export function F(options?: any): PropertyDecorator {
    return (target, name) => {
        // do something.
    }
}

et en finir, mais à la place, je dois faire quelque chose comme l'exemple brut suivant :

export function F(...args: any[]): any {
   var func = (target, name) => {
      // do something.
   }

   if (args.length === 1) return func;
   else if (args.length === 2) func(args[0], args[1]);
}

Ce qui est une vraie douleur.

@Tharaxis ce comportement est par conception. @F et @F() ne sont pas équivalents. @F appelle un décorateur F , où @F() appelle un décorateur facteur F avec une liste d'arguments vide, puis applique le décorateur résultant.

Le problème ici est que le compilateur compare la signature de la fabrique de décorateurs F à declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; qui finit par être assignable. Je pense que nous avons besoin d'un contrôle plus strict ici pour nous assurer que nous attrapons ces problèmes. J'ai enregistré #3246 pour résoudre ce problème. Je pense que vous faites la bonne chose et que le compilateur devrait détecter les utilisations non valides d'une usine de décorateurs.

@mhegazy Je ne sais pas pourquoi c'est le cas cependant, existe-t-il un cas d'utilisation qui empêche de faire l'équivalent de @F et @F() , car en fin de compte, ils sont tous deux une invocation identique de la fonction de décorateur , il se trouve que l'un d'entre eux inclut également une invocation à une fonction d'usine externe ? Il semble que le principe du moindre étonnement soit violé ici car il s'agit certainement d'un comportement assez étonnant et je ne vois pas pourquoi il devrait y avoir 2 façons d'invoquer un décorateur du point de vue du consommateur d'API.

Bien que je comprenne que du point de vue JS, il y a une différence (dans l'un, vous passez la référence de la fonction F et dans l'autre, vous exécutez une fonction, puis vous passez la référence de son résultat en tant que décorateur), je ne suis pas Bien sûr, je comprends pourquoi on ne peut pas simplement avoir des décorateurs passés comme @F qui correspondent implicitement (et invoquent) l'usine comme une avec une liste de paramètres vide ?

Le compilateur n'a aucun moyen de savoir s'il s'agit d'une usine ou d'un décorateur. si vous avez cette signature :

declare function decoratorOrFactory (...args: any[]): any;

Est-ce un décorateur ou est-ce une usine, faut-il l'appeler avec des arguments vides ou non ?

Tout ce que le compilateur peut faire est de vérifier que la signature correspond lorsqu'il l'appelle d'une manière ou d'une autre, et de donner des erreurs si ce n'est pas le cas.

@rbuckton a un correctif pour #3246 qui devrait

@mhegazy , ce que je dis, c'est que l'utilisation d'un décorateur (c'est- @F dire @F() ) devrait _toujours_ être une invocation de la fonction d'usine F, qui à son tour renvoie le type de décorateur approprié. Il n'y a pas de représentation de haut niveau d'un décorateur sans usine d'encapsulation.

Autrement dit la signature de F est toujours :

declare function F(...args: any[]): ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator;

Ou un équivalent.

Ensuite, l'invocation @F est l'équivalent de @F() par le compilateur. Étant donné que la signature de @F et de @F() doit correspondre à la signature d'usine sous une forme quelconque, cela résout le problème de confusion d'invocation. Il n'y a qu'une seule façon d'invoquer un décorateur !

Compte tenu de ce qui suit :

declare function F(...args: any[]): PropertyDecorator {
    return (target, name) => {
        // do stuff.
    }
}

@F()
property: number;

et

declare function F(target, name) { // Matches PropertyDecorator
    // do stuff.
}

<strong i="22">@F</strong>
property: number;

Ils font la même chose mais je dois les déclarer différemment ! Cependant, déclarer F pour qu'il puisse exister dans les deux sens est vraiment fastidieux et rend impossible la création de paramètres et de documentation cohérents. Vous choisissez soit que votre décorateur utilise une usine ou non, et votre consommateur d'API doit connaître cette distinction à l'avance pour l'utiliser.

@Tharaxis Si je regarde une bibliothèque fournissant F qui sera utilisée comme décorateur, je m'attends absolument à ce que @F et @F() soient des choses différentes. Tout comme je m'attendrais C ce que C() soient des choses différentes - le premier fait référence à une valeur, le second invoque une valeur. @F applique un décorateur, @F() invoque une fonction qui créera un décorateur. Ce ne sont pas, et ne devraient pas être, la même opération.

@DavidSouther y a-t-il une raison valable pour laquelle cela ne peut pas ou ne devrait pas être le cas ? Il n'y a pas de différence fonctionnelle entre une invocation de @F et @F() mais il y a une assez grande différence entre la façon dont elles sont définies et documentées, et cela ajoute une complexité supplémentaire à l'invocation qui devrait être inutile.

Ma proposition est qu'il ne devrait pas y avoir deux façons de définir un décorateur parce qu'il n'y a pas besoin, et il ne devrait pas y avoir besoin de définir un décorateur plus d'une façon.

Il n'y a pas de différence fonctionnelle entre une invocation de @F et @F()

Je pense que c'est le principal point de discorde. Je ne pense pas que ce soit vrai. F est une référence à une fonction, F() est une référence à la valeur de retour de F lorsqu'elle est appelée avec un jeu d'arguments vide (c'est- F.call(this, []) dire

@Tharaxis @F et @F() sont des choses différentes. Il y a une différence dans la façon dont ils sont utilisés, une différence dans la façon dont ils sont documentés et des différences dans leur invocation qui est absolument nécessaire.

Permettez-moi de poser une autre question : pourquoi chaque fonction de décorateur doit-elle être une usine ? A l'aide de votre proposition, il serait impossible d'avoir un simple décorateur.

@mhegazy alors que je reconnais que cette dernière option ( @F() ) entraîne la génération d'une fermeture fonctionnelle et d'une surcharge supplémentaire via la création de fonctions de décorateur isolées (et dupliquées) et l'invocation @F fonctionne essentiellement par rapport à une référence de fonction de décorateur partagée, je dirais que l'isolement par la fermeture serait "plus sûr" et plus cohérent, réduisant ainsi le risque de surprises.

@DavidSouther Votre question dépend de ce que vous définissez comme simple décorateur. Rien n'empêche d'avoir un simple décorateur sous la forme de :

declare function F(): PropertyDecorator {
    return (target, name) => {
        // do stuff
    }
}

Je trouve cela assez peu invasif car il ne s'agit que de 2 lignes de plus que la "syntaxe simple" et définit plus clairement quelles sont les invocations de décorateur disponibles. De plus, étant donné qu'il n'y a alors qu'une seule façon de définir un décorateur (et ne négligeons pas la valeur de la cohérence), il vous faudra probablement environ 2 secondes supplémentaires pour le sortir par rapport à la "syntaxe simple".

De plus, un problème plus problématique est le suivant :

declare function F(options?: any): PropertyDecorator {
    return (target, name) => {
        // do stuff
    }
}

@F()
property1: number;

<strong i="15">@F</strong>
property2: number;

@F({ option: true })
property3: number;

Une de ces choses n'est pas comme les autres. L'utilisation de @F ne fonctionnera pas même si, à première vue, cela devrait certainement le faire. Gardez à l'esprit, prétendez que je suis quelqu'un qui ne sait pas à quoi ressemble la déclaration sous-jacente de F, mais sachez simplement que F existe et qu'il peut prendre un ensemble facultatif d'arguments. Les chances que je fasse une erreur avec @F ne sont pas négligeables dans le mécanisme actuel. Cela impose une grande responsabilité au développeur/documenteur de s'assurer que le consommateur est conscient que @F ne fonctionnera pas (mais peut-être que @C fera parce que c'est "différent" en quelque sorte).

Si je veux un décorateur « taille unique », où le consommateur n'a pas à s'inquiéter, je dois faire cette chose horrible :

declare function F(...args: any[]): any {
    var decorator = (target, name) => {
        // do stuff
    }

    // Heaven forbid your decorator formal parameter list also can take 2 parameters.
    return (args.length === 2) ? decorator(args[0], args[1]) : decorator;
}

Ce qui est franchement horrible. De plus, je perds tout l'intellisens des paramètres précieux contre le décorateur F car il doit maintenant être généralisé.

Il y a beaucoup à dire sur le fait que les décorateurs soient faciles et cohérents à définir et à documenter. Ne nous leurrons pas en pensant que tous ceux qui consomment notre code auront le même sens ou la même compréhension que nous qui sommes les créateurs de ce code.

@Tharaxis J'y ai pensé dès le début de la conception pour les décorateurs, et je comprends vos points spécifiques. Comme @mhegazy et @DavidSouther le mentionnent précédemment dans ce fil, ce comportement est cohérent avec le comportement standard des fonctions en JavaScript.

Vous avez raison de dire que toute tentative de création d'une fonction pouvant servir à la fois de décorateur et d'usine de décorateurs peut être quelque peu complexe. En général, cependant, il peut être préférable d'employer une bonne pratique pour toujours utiliser des usines de décoration et de fournir des règles à un linter pour s'assurer que cette pratique est adoptée.

De plus, je viens de soumettre le PR #3249 pour répondre à votre point précédent concernant les incohérences dans la vérification de type pour les décorateurs.

Salut @rbuckton ,

Je fais partie de ces utilisateurs moins avertis des décorateurs mentionnés par @Tharaxis...

Je suppose que la question que je me poserais est de savoir si les décorateurs doivent être cohérents avec les fonctions ? C'est-à-dire que dans le cas des fonctions, renvoyer this.f par rapport à this.f() a tout son sens pour moi car parfois je veux la valeur, parfois je veux la chose qui produit la valeur.

Dans le cas des décorateurs, cela ne me semble pas aussi clair au niveau des fonctionnalités linguistiques. Je voudrais faire valoir que je veux juste appliquer le décorateur @F , je ne veux pas vraiment savoir s'il a été implémenté en tant que méthode d'usine ou statique. C'est à moins que je perde une certaine capacité que les décorateurs auraient autrement.

Mes excuses si ce qui précède est mal informé ou ignorant, je suis relativement nouveau dans le monde javascript.

Merci et bravo

Je dois également poser cette question - est-il logique de maintenir la parité syntaxique avec les fonctions _if_ (et seulement si) il n'y a aucune raison réelle de le faire autre que "parce que c'est défini à l'aide de fonctions". Je vois les décorateurs comme une fonctionnalité totalement indépendante des fonctions qui _arrivent_ simplement à être définies en les utilisant.

À l'heure actuelle, le fait que @F et @F() ne soient pas identiques provoque les problèmes suivants :

  • Les développeurs ayant besoin de comprendre la différence entre @F et @F() et pourquoi parfois cela peut fonctionner et pourquoi parfois cela peut ne pas fonctionner (et la nécessité d'utiliser @F contre @F() pourrait être entièrement spécifique au framework/code/usage - donc incohérent du point de vue de la consommation : imaginez declare function A(params?: any): ParameterDecorator vs declare function B(target, name) , @A ne fonctionnera pas, mais @A() , @B fonctionnera mais @B() ne fonctionnera pas, pourtant cognitivement c'est la même chose, une application d'un décorateur sans arguments).
  • Résoudre le problème à partir d'un niveau de code nécessite une solution de contournement lourde qui supprime la capacité de documenter de manière adéquate la syntaxe d'appel du décorateur.
  • La refactorisation demande un peu plus de travail si vous avez créé un « décorateur brut » ( @F ) et que vous souhaitez maintenant ajouter des paramètres (mais les rendre facultatifs pour une compatibilité descendante). Sous la méthode actuelle, tous ces @F s'appliquent devront maintenant être refactorisés en @F() - certains dans le code auquel vous n'aurez peut-être pas accès - tandis que l'invocation implicite vous permettrait de continuer à fonctionner et de restreindre le modifier votre code.

@Tharaxis Je comprends que la distinction entre @F et @F() peut être un lourd fardeau cognitif pour les consommateurs. Cependant, il semble que vous demandiez que le code émis, et donc le comportement, change, afin que les deux se comportent de manière équivalente. Cela serait probablement en conflit avec la proposition ES7 pour les décorateurs, car il est très peu probable que les deux formes soient traitées de la même manière. Ce ne sera pas bien si cela conduit à une divergence.

Un autre point est que la confusion ici s'apparente à la confusion entre passer une fonction en tant que rappel et appeler la fonction. Si tu avais ça

function callbackFactory() {
     return function(...args: any[]) { // do stuff };
}
function takesCallback(cb: (...args: any[]) => void) { }
takesCallback(callbackFactory());

Un utilisateur peut devenir confus et appeler takesCallback(callbackFactory) sans invoquer callbackFactory . C'est vraiment comme la confusion que vous avez signalée avec les décorateurs, mais si le langage normalisait ces deux formes pour faire la même chose, il y aurait une perte d'expressivité. Parfois, la distinction est importante et doit être maintenue. Je ne sais pas si cela s'applique également aux décorateurs, mais en théorie, cela pourrait certainement.

@JsonFreeman, je ne

En ce qui concerne votre exemple concernant les fonctions, il semble encore une fois que nous confondons des fonctions avec des décorateurs alors qu'elles sont en réalité (et devraient être) des constructions complètement différentes. Lorsque je travaille avec une fonction en JavaScript, je comprends comment les fonctions sont utilisées et je comprends la différence entre une référence de fonction et un appel de fonction (et quand je pourrais vouloir utiliser l'une par rapport à l'autre) - et quand je décris quelque chose en tant que fonction, il n'y a pas de confusion quant à ses usages. Les décorateurs ne sont pas des fonctions et ne doivent pas hériter du comportement des fonctions. Les fonctions ne sont que la méthode sur laquelle les décorateurs sont construits. C'est comme dire que les classes sont des fonctions, ce qui n'est pas le cas, les fonctions sont simplement la façon dont nous décrivons les classes avant ES6 car il n'y avait pas de méthode plus évidente disponible. Les classes ne prennent pas les propriétés des fonctions, vous ne pouvez pas appeler des classes comme des fonctions, mais vous les déclarez en utilisant des fonctions antérieures à ES6.

Quoi qu'il en soit, j'ai l'impression d'avoir épuisé mes arguments sur la question. Je vais devoir contourner le choix. Dans mon propre code, j'utiliserai toujours des usines pour ma propre cohérence. Je pense toujours que traiter les décorateurs littéralement comme des fonctions est susceptible de causer beaucoup de frustration à l'avenir, mais ce n'est que moi.

@mhegazy C'est le même problème que j'ai soulevé sur le dépôt du décorateur https://github.com/jonathandturner/decorators/issues/16 Je pense que c'est quelque chose auquel il faut penser. Dans l'état actuel des choses, je m'attends à ce que ce choix de conception fasse l'objet de nombreux articles de blog intitulés quelque chose de similaire également "ES7 Decorator Pitfalls".

@Tharaxis , je suis d'accord avec votre premier point. Je ne pense pas qu'une proposition ES7 devrait nous empêcher de bien concevoir quelque chose, et notre conception est en effet l'un des précurseurs d'ES7 de toute façon.

Quant à l'argument "les décorateurs ne sont pas des fonctions", si je ne me trompe pas, nous ne parlons pas des décorateurs eux-mêmes, nous parlons des usines de décorateurs. Et indépendamment de toute distinction entre les décorateurs et les fonctions, je pense que les _usines_ des décorateurs ne sont en fait que des fonctions. Et il semble que vous demandiez au compilateur de générer automatiquement un appel à une fabrique de décorateurs, ce qui est une fonction ordinaire. Si cela se produisait, le programmeur n'aurait aucun moyen de faire la distinction entre l'usine de décorateurs et le décorateur.

De plus, l'avantage de traiter les décorateurs comme des fonctions est qu'un consommateur peut l'appliquer en tant que décorateur ou simplement en l'appelant comme bon lui semble.

@JsonFreeman l'argument tourne autour de la situation actuelle où il existe deux façons de décrire et d'invoquer un décorateur, l'une via une fonction d'usine, puis l'autre en tant que fonction "brute" (qui dans le cas de l'usine est le résultat d'un appel d'usine ), et la question portait davantage sur « ne devrait-il pas y avoir qu'un seul moyen et que les usines soient ainsi ».

Ce que je demande, c'est oui, ne pourrions-nous pas simplement faire en sorte que le compilateur transforme @F en l'équivalent de l'appel @F() et que la vérification de type nécessite une liste d'arguments de 0...n paramètres lors de l'utilisation du syntaxe sans crochet. Peut-être pourriez-vous préciser ce que signifie "... il n'y aurait aucun moyen pour le programmeur de faire la distinction entre l'usine de décorateurs et le décorateur...", car je pense que c'est très facile à distinguer. Le décorateur est toujours la réponse de l'usine, et le nom de l'usine est le nom du décorateur... pas si difficile, ou ai-je mal compris ?

Quant à votre dernier point sur le fait de permettre au consommateur d'appliquer simplement le décorateur, s'il est bien décrit que tous les décorateurs utilisent des usines, il est assez facile d'invoquer ensuite un décorateur vous-même, vous faites juste <decorator name>(<argument list>)(target, name) par rapport à <decorator name>(target, name) comme exemple. Gardez à l'esprit que rendre obligatoire l'utilisation d'usines signifierait que le premier exemple fonctionnera toujours, tandis que ne pas le rendre obligatoire entraînera parfois le dernier exemple, et dépendra entièrement de la façon dont le décorateur est mis en œuvre - un casse-tête qui n'attend que de se produire.

Je pense qu'il est nécessaire de souligner que je n'ai aucun problème avec les décorateurs utilisant des fonctions, mon problème est cependant qu'avoir deux manières de décrire la même chose entraîne des problèmes de cohérence. Surtout quand ces deux manières signifient également que votre méthode d'invocation doit différer. Il s'agit d'une disparité qui entrave tout, de la refactorisation à la cohérence du langage.

Le problème de refactorisation que j'ai décrit il y a quelques articles devrait déjà être une raison plus que suffisante pour laquelle la méthode actuelle doit être inspectée.

Salut à tous, juste un dernier centime d'un utilisateur régulier dans le sens de ce que @Tharaxis a dit.

Je serais plus que satisfait de la proposition actuelle si :
a) l'univers l'exige.
b) les décorateurs perdront des capacités importantes si les choses étaient différentes.

Ce dernier est bien sûr un jugement de valeur, dont la réponse dépendra dans une certaine mesure du type de développeur que vous êtes (c'est-à-dire utilisateur général / utilisateur expert, etc.). Je fais partie de la première catégorie et je suis généralement dispersé dans plusieurs langages et frameworks. Donc, pour moi, les « capacités importantes » n'incluront pas la possibilité d'écrire des décorateurs de 2 manières différentes. Des exemples de tous les compromis seraient formidables s'ils existaient.

Idéalement, ce serait formidable si @F et @F() pouvaient être cohérents quelle que soit la façon dont les décorateurs sont implémentés, mais sinon, je préférerais de loin être contraint d'utiliser des usines lors de l'écriture de décorateurs plutôt que d'avoir à éviter les râteaux dans l'herbe chaque fois que je les utilise.

Merci et bravo

Il semble que cette demande repose sur l'idée que les usines de décorateurs sont le moyen canonique d'utiliser des décorateurs, mais je ne vois pas comment cela permet à l'utilisateur de définir et d'utiliser des décorateurs bruts. Si je définis un décorateur F et que l'application @F est traitée comme @F() , le résultat de l'appel de F est utilisé comme décorateur, au lieu de F lui-même. Suggérez-vous que nous donnions une erreur quelque part si quelqu'un essaie d'appliquer un décorateur brut au lieu d'une usine de décorateurs ?

Cette idée donne l'impression qu'elle inverserait la composition naturelle des décorateurs et des usines de décorateurs. Les décorateurs se sentent définitivement comme la construction primitive ici, les usines de décorateurs étant une couche d'abstraction construite au-dessus des décorateurs. Ils ne sont qu'un modèle, rien de plus. Si à la place l'usine de décorateurs devenait la chose canonique, la primitive, alors les gens définiraient un tas d'usines de décorateurs, qui ne prennent aucun argument et renvoient un décorateur plat. Cela commencerait à sembler très idiot, et cela inverserait l'intuition naturelle de ce qui est considéré comme basique et de ce qui est considéré comme plus complexe.

Une chose dont je me méfie beaucoup avec les décorateurs est un excès de magie. Personnellement, je deviens nerveux quand je ne comprends pas ce que fait le code, et si le compilateur ajoute secrètement des invocations supplémentaires que le programmeur n'a pas écrites, cela me semble trop vaudou.

Salut @JsonFreeman ,

Comme je l'ai mentionné, ma préférence serait toujours que la laideur soit du côté de l'auteur du décorateur plutôt que de l'utilisateur. Cependant, je suis d'accord que beaucoup d'usines sans arg sont très moches. Un décorateur pourrait-il être utilisé pour résoudre ce problème? Par exemple

// wraps rawDecoratorMethod in a no-arg factory method.
<strong i="8">@Decorator</strong>
function rawDecoroatorMethod(target, name, descriptor) {...}
// looks like this one would be a no-op... so that feels not quite right unless there's other advantages.
<strong i="11">@DecoratorFactory</strong>
function decoroatorFactoryMethod(someArg) {...}

Quoi qu'il en soit, si une telle approche pouvait s'avérer logique, elle présente l'avantage que l'annotation documente le but de la fonction. c'est-à-dire qu'il décore.

À votre santé

Bonne sauce, décorateurs pour aider à définir les décorateurs, un cas grave de Typeception.

@JsonFreeman Je ne suis pas sûr qu'avoir des fonctions à

L'autre option consiste à exiger que les décorateurs appelés @F puissent correspondre au modèle de ClassDecorator, MethodDecorator, PropertyDecorator ou ParameterDecorator, OU à une fonction d'usine 0..n arg qui renvoie ClassDecorator, MethodDecorator, PropertyDecorator ou ParameterDecorator. Cependant, j'aurais l'impression que cette implémentation provoquerait d'autres erreurs (et si vous avez deux fonctions en conflit, laquelle serait considérée comme la meilleure correspondance possible ?) Et ne ferait qu'ajouter une complexité excessive au sein du compilateur. La simple conversion des appels @F en @F() serait la solution la plus simple et résoudrait les différents problèmes énoncés.

Désolé, pour clarifier, je ne prétendais pas que votre solution est complexe. Je voulais dire qu'invoquer automatiquement le décorateur avant de l'appliquer est opaque. L'invocation insérée est invisible pour l'utilisateur, et j'ai l'impression que de nombreux utilisateurs ne s'y attendent pas.

Je pense que la façon dont il se présente actuellement n'est pas complexe non plus. Comme vous le soulignez, cela permet aux auteurs de bibliothèques d'être vagues ou insipides quant à savoir si leur fonction est censée être appliquée en tant que décorateur. Que je t'accorde. Mais une bonne bibliothèque le fera comprendre.

Dans le schéma que vous proposez, où vous effectuez l'invocation automatiquement, que se passe-t-il lorsque vous utilisez une expression de décorateur arbitraire au lieu d'un simple identificateur ? Est-ce que cela est invoqué aussi?

Je suis d'accord que, sauf indication contraire, l'invocation implicite est susceptible d'être surprenante, mais uniquement pour ceux qui n'ont pas utilisé de concepts tels que les attributs C# et autres.

Pourriez-vous préciser ce que vous entendez par une expression de décorateur arbitraire ?

Bonne sauce, décorateurs pour aider à définir les décorateurs, un cas grave de Typeception.

Appel juste. Me sert bien pour un commentaire mal pensé tard un après-midi de week-end. Leçon apprise. Internet.undo().

Ce que je veux dire, c'est que s'il s'avère que la syntaxe cohérente du site d'appel impose l'utilisation d'usines, j'en suis plus que satisfait. J'écrirai sans doute à un décorateur pour enlever la plaque de la chaudière. Encore une fois, je préférerais un peu de douleur lors de l'écriture du décorateur à une douleur répétée en les utilisant (bien que la douleur potentielle à ce stade). D'autres seront en désaccord.

N'y a-t-il pas également le problème de l'amélioration de l'API ? Un décorateur créé sans usine ne peut pas avoir de paramètres facultatifs ajoutés ultérieurement, c'est pourquoi je prédis @F et @F2() dans mon futur.

Je ne vois pas non plus que vous obtiendriez ce scénario en utilisant f et f() , ce sont des cas d'utilisation différents du côté de la consommation. Dans le cas du décorateur, j'applique/invoque toujours le décorateur sur la cible immédiatement et là, et il y a toujours une invocation de méthode en coulisse.

Mais le nœud de tout cela pour moi est le problème de la convivialité, lorsque j'applique un décorateur, je ne veux pas avoir à chercher sur Google pour savoir comment l'auteur l'a mis en œuvre - je m'intéresse au comportement, pas à l'emballage.

À votre santé

Juste un dernier mot pour résumer et ensuite je me tais. En bref, ce n'est vraiment qu'un problème de conception d'interaction pour moi. J'ai vraiment hâte de voir des décorateurs conçus de l'extérieur vers l'intérieur et non l'inverse.

En tant que gars UI/UX, je vois cela assez souvent. J'ai travaillé avec des développeurs talentueux qui ont suggéré des solutions d'interface utilisateur qui nuiraient aux utilisateurs (un exemple était deux boutons de sauvegarde dans un cas pour résoudre une complexité de transaction - encore une fois un gars intelligent et un bon humain). Je pense juste que lorsque vous êtes plongé dans la logique complexe des détails d'implémentation, il peut être difficile d'oublier ce que vous savez et de le voir à travers les yeux d'un utilisateur moyen.

Maintenant, si, en moyenne, je me trompe tout simplement, ou si la complexité impose la conception actuelle, alors tout va bien, je n'aurai qu'à me mettre en route et à apprendre.

À votre santé

Si votre décorateur n'est pas un identifiant, mais une autre expression, l'invoqueriez-vous automatiquement ? Par exemple, disons que c'était un décorateur. OU-ed avec une expression de fonction de secours. Quelque chose comme:

@(F || function (target) { // default decorator behavior })
class C { }

L'invoquez-vous alors automatiquement ? Cela vous donnerait un mauvais résultat car vous finirez par appeler le décorateur par défaut avant qu'il ne soit appliqué :

(F || function (target) { // default decorator behavior })()

Cela ne semble pas correct.

@JsonFreeman , la syntaxe du décorateur autorise-t-elle des expressions arbitraires comme celle-ci ? Je ne suis pas sûr à 100% de permettre aux développeurs d'avoir autant de corde avec laquelle se pendre est nécessairement une bonne idée, mais c'est juste moi (purement parce que je ne vois pas d'expressions arbitraires comme celle-ci résolvant le problème de réutilisation/passe-partout que les décorateurs visent à résoudre et au lieu de cela, il ne sert qu'à rendre le code moche et encore plus difficile à suivre).

Cela dit, je suppose que la seule façon dont cela fonctionnerait serait que l'expression arbitraire elle-même s'évalue à la même syntaxe d'usine, donc :

@(F || () => function(target) { /* default decorator behaviour */ })
class C { }

Je suis d'accord que cela vous oblige à mettre quelques éléments plus compliqués autour de votre décorateur arbitraire, mais je pense que l'un d'eux a de plus gros problèmes que l'ajout de () => si vous utilisez des expressions arbitraires comme décorateurs.

REMARQUE : Je ne voulais évidemment pas dire que la syntaxe lambda serait le seul moyen de le déclarer, mais () => est légèrement plus agréable que function() { return function(target) { /*...*/ } } .

Vraiment désolé d'interrompre ce débat sur la syntaxe de l'appel du décorateur, mais quelqu'un pourrait-il officiellement clarifier l'ordre dans lequel les expressions du décorateur sont appelées ? En particulier, selon le type de cible décorateur, la position de l'élément cible dans la source d'origine et l'ordre des décorateurs sur une seule cible.

@billccn Les décorateurs sont appliqués de bas en haut. Donc dans le code source original si vous aviez

class C {
    <strong i="7">@F</strong>
    <strong i="8">@G</strong>
    method() { }
}

Cela appliquerait d'abord G à la méthode, puis appliquerait F au résultat. C'est ce que tu demandes ?

Et pour ça :

<strong i="6">@A</strong>
class Clazz {
    <strong i="7">@B</strong>
    prop = 1;

    <strong i="8">@C</strong>
    method() {}

    <strong i="9">@D</strong>
    get prop() {return 1;}

    <strong i="10">@E</strong>
    method2() {}

    <strong i="11">@F</strong>
    prop2 = 1;
}

ils suivent l'ordre des portées, donc toutes les propriétés/méthodes d'abord, dans l'ordre de déclaration, puis celle de la classe. c'est-à-dire B, C, D, E, F, A.

vous pouvez voir le code généré ici .

Il semble qu'il ne soit actuellement pas possible de décorer les propriétés des paramètres. Peuvent-ils être pris en charge ?

Les propriétés de paramètre sont des choses comme constructor(private prop: Type) , où une propriété (champ) et un paramètre sont déclarés ensemble. Le problème est que les deux peuvent être décorés, il faudra donc peut-être inventer une syntaxe imaginative.

Question : Les décorateurs peuvent-ils être utilisés pour modifier le comportement d'une méthode d'objet simple, comme...

var isCreatorUser = () => {  /* code that verifies if user is the creator */ },
    isAdminUser = () => { /* code that verifies if user is admin */ },

var routeConfig = {
    get() {},
    <strong i="7">@isCreatorUser</strong> patch() {},
    <strong i="8">@isAdminUser</strong> <strong i="9">@isCreatorUser</strong> delete() {}
};

... et sinon pourquoi ? Ce sera vraiment utile.

Pas encore, mais nous y réfléchissons. @rbuckton pourrait avoir plus de détails.

Puis-je demander pourquoi le décorateur ne peut pas changer le type de résultat décoré par rapport au type en cours de décoration ?

Par exemple, MethodDecorator peut être alternativement proposé comme ci-dessous :

declare type MethodDecorator = <T, R>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<R> | void;

Ainsi, le décorateur peut être plus flexible à utiliser et fonctionne un peu comme une macro. Le type de classe/champ décoré peut être mis à jour en fonction du type de retour du décorateur, de sorte que la sécurité de type soit toujours conservée.

Je sais que https://github.com/jonathandturner/brainstorming/blob/master/README.md#c4 -defining-a-decorator a déclaré que le type de retour devrait être le même. Mais type validity n'est pas un argument valide ici, à mon humble avis. Premièrement, les décorateurs en JavaScript peuvent modifier le type de classe/champ, il n'y a donc pas de problème de compatibilité entre TS et JS. Deuxièmement, les décorateurs sont appliqués de manière statique, le compilateur TS peut raisonner sur le type de champ d'origine et le type de champ décoré sur le site défini.

@HerringtonDarkholme Je dirais que cela doit être suivi dans une suggestion différente. La raison principale est la façon dont le compilateur raisonne sur les types à travers les déclarations. une fois qu'un type est déclaré, il ne peut plus changer. l'ajout de mutateurs de type dans le mix signifie que nous devons les résoudre car nous résolvons la déclaration qui peut devenir velue.

@mhegazy donc, puis-je comprendre cela comme un compromis pour la facilité de mise en œuvre, plutôt que comme une conception délibérée ?

Selon cette proposition , les décorateurs restaurent la possibilité d'exécuter du code au moment de la conception, tout en conservant une syntaxe déclarative. Le code suivant doit donc être
valide dans le JavaScript du futur, mais non valide dans le TypeScript d'aujourd'hui (cependant, cela pourrait être accepté par TS un jour, n'est-ce pas ?).

function SafeCtor(target) {
  var safe = function(...args) {
    var instance = Object.create(target.prototype)
    target.call(instance, ...args)
    return instance
  }
  safe.prototype = target.prototype
  return safe
}

<strong i="10">@SafeCtor</strong>
class Snake {
  constructor(name) {
    this.name = name
  } 
}

var snake = Snake('python')
alert(snake instanceof Snake)

Je sais que les chances de prendre en charge cette fonctionnalité sont faibles. Mais je veux confirmer qu'il ne s'agit pas d'une histoire similaire nominal dactylographie de structural vs nominal .

La vérification actuelle est que vous devez retourner quelque chose de votre décorateur qui est assignable à la cible. la partie manquante est que nous ne capturons aucun ajout au type. Je ne pense pas que nous ayons une idée de la façon de procéder, mais nous n'y avons pas vraiment réfléchi non plus. donc je pense que c'est une suggestion juste, mais le coût de sa mise en œuvre peut être très élevé.

Idéalement, je pense que le type renvoyé par le décorateur devrait être identique à la cible. On ne sait pas si vous utiliserez le type décoré dans une position source ou cible dans une affectation.

Bien que je suppose que nous devrions vraiment fusionner le type de retour du décorateur et la cible, un peu comme un mixin.

Juste une idée de fonctionnalité supplémentaire. Étant donné que la sortie du compilateur TypeScript est JavaScript, il existe des langages similaires pour Java, comme Xtend

Vous pouvez découvrir quelques idées intéressantes sur ce projet. Recherchez les annotations actives "Les annotations actives permettent aux développeurs de participer au processus de traduction du code source Xtend en code Java via la bibliothèque"

Contrôler la sortie TypeScript via des décorateurs sera une fonctionnalité vraiment intéressante !

Les décorateurs pour les interfaces devraient être possibles.

@shumy Le problème est que les interfaces n'existent en aucun cas au moment de l'exécution et que les décorateurs s'exécutent uniquement au moment de l'exécution.

Existe-t-il une raison de traiter les décorateurs de propriétés différemment des décorateurs de méthodes en ce qui concerne la valeur de retour ? (Babel ne le fait pas).

Ce que je veux dire, c'est que si je veux définir une fonctionnalité pour une propriété via un décorateur, je dois faire ceci :

function decorator(proto, name) {
    Object.defineProperty(proto, name, { value: 42 });
}

class Foo {
    <strong i="7">@decorator</strong>
    a: number;
}

Tandis que Babel demande le retour du descripteur :

function decorator(proto, name) {
    return { value: 42 };
}

class Foo {
    <strong i="11">@decorator</strong>
    a;
}

Cela rend difficile l'écriture d'un décorateur en TypeScript qui fait partie d'une bibliothèque qui sera utilisée à partir de Babel.

Je proposerais qu'un décorateur de propriété soit traité de la même manière que le décorateur de méthode, et que la valeur de retour du décorateur soit appliquée via Object.defineProperty . Cela permettrait également d'avoir plusieurs décorateurs sur une même propriété, tout comme une méthode.

Une solution de contournement pour l'instant (pour la compatibilité Babel) consiste à renvoyer le descripteur du décorateur en plus de définir la propriété, mais cela semble inutilement inutile :

function decorator(proto, name) {
    var d = { value: 42 };
    Object.defineProperty(proto, name, d);
    return d;
}

@mhegazy auriez-vous un aperçu de mon commentaire ci-dessus ?

@sccolbert Dans TypeScript, nous avons choisi d'interdire l'utilisation de descripteurs de propriété pour les décorateurs sur les propriétés de données, car cela peut entraîner des problèmes lors de l'exécution en raison du fait que toute "valeur" spécifiée par le descripteur sera définie sur le prototype et non sur l'instance . Bien que votre exemple ci-dessus ne soit généralement pas un problème, tenez compte des éléments suivants :

function decorator(proto, name) {
  return { value: new Point(0, 0); }
}

class Foo {
  <strong i="7">@decorator</strong>
  p: Point;

  setX(x) { this.p.x = 1; }
}

let a = new Foo();
let b = new Foo();
console.log(a.p.x); // 0
b.setX(10);
console.log(a.p.x); // 10 (!)

Il existe une proposition pour une future version d'ES pour prendre en charge une propriété « initializer » sur le descripteur, qui serait évaluée pendant le constructeur. Cela vous donnerait la possibilité de contrôler si la valeur est définie sur le prototype ou allouée par instance.

Pour l'instant, vous pouvez contourner cette limitation en appelant directement Object.defineProperty , comme dans l'exemple de votre commentaire. Nous continuerons à étudier s'il faut assouplir cette restriction à l'avenir.

@rbuckton merci ! Êtes-vous en pourparlers avec Babel pour converger sur la même sémantique ? Je pense que c'est la chose la plus importante.

Pourquoi serait-il utile d'utiliser un décorateur afin de spécifier une valeur pour une instance particulière ? N'est-ce pas à cela que sert l'initialiseur de propriété ?

Mon cas d'utilisation est plus compliqué que l'exemple. J'utilise en fait le décorateur pour définir un getter qui renvoie un objet lié au contexte this de la propriété, afin que les méthodes sur cet objet aient accès à l'instance sur laquelle il a été défini.

Vous pouvez considérer cela comme une émulation du protocole de descripteur en Python, où l'accès à la méthode bar définie sur la classe Foo via l'instance foo (c'est- foo.bar dire __get__ méthode de la fonction qui renvoie un BoundMethod . Lorsque cet objet est appelé, la fonction sous-jacente est invoquée avec self comme premier argument, qui dans ce cas est foo . Javascript n'a pas ce concept, c'est pourquoi nous devons faire circuler thisArg et appeler function.bind partout.

Pour mon cas, je ne définis pas de méthodes, mais un signal de type sûr. Voici le décorateur :
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L144

Lorsque la propriété est accédée, elle renvoie une implémentation de ISignal qui est liée au contexte this du propriétaire. Cela permet au signal de se référer à l'instance sur laquelle il a été défini :
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L263

Donc en effet, j'utilise le décorateur comme raccourci pour cet équivalent verbeux :

class Foo {
  valueChanged: ISignal<number>;
}

defineSignal(Foo.prototype, 'valueChanged');

@rbuckton

Les décorateurs ne sont pas autorisés lorsqu'ils ciblent ES3

Pourquoi? Je ne vois rien qui empêcherait l'utilisation de __decorate dans ES3.

Il utilise le descripteur de propriété, qui n'est pas disponible dans ES3
Le 4 sept. 2015 14:03, "Koloto" [email protected] a écrit :

@rbuckton https://github.com/rbuckton

Les décorateurs ne sont pas autorisés lorsqu'ils ciblent ES3

Pourquoi? Je ne vois rien qui empêcherait l'utilisation de __decorate dans ES3.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/2249#issuecomment-137717517
.

Les navigateurs

@cybrown Oui, il utilise le descripteur de propriété pour les méthodes et les accesseurs. J'ai testé sur des propriétés simples (champs sans accesseurs) uniquement. Mais il semble que les décorateurs puissent être autorisés dans ES3 pour les propriétés (sans accesseurs) et les classes. Ce serait utile.

Et un faux descripteur de propriété peut être utilisé pour les méthodes lors du ciblage de ES3. Quelque chose comme { writable: true, enumerable: true, configurable: true } . Je ne vois donc aucune raison de ne pas prendre en charge ES3.

@sccolbert je vois. Ça a du sens. Incidemment, TypeScript travaille sur une amélioration du typage _this_ pour les fonctions et les méthodes. Je me demande si cela serait d'une quelconque aide ici. Bien que je suppose que la saisie n'est pas le problème pour vous, c'est la sémantique d'exécution.

@JsonFreeman L' amélioration de la saisie de _this_ semble intrigante pour certains de mes autres cas d'utilisation. Avez-vous plus d'infos là-dessus ?

Je pense que la discussion la plus développée sur _this_ dactylographie est au #3694.

À votre santé!

erreur TS1207 : les décorateurs ne peuvent pas être appliqués à plusieurs accesseurs get/set du même nom.

<strong i="6">@get</strong>
public get myValue():any{...}

<strong i="7">@set</strong>
public set myValue(value:any){...}

le code ci-dessus n'est pas autorisé même s'il a plus de sens par rapport à

<strong i="11">@get</strong>
<strong i="12">@set</strong>
public get myValue():any{...}

public set myValue(value:any){...}

Getter et setter sont définis dans un appel à Obect.defineProperty. C'est plutôt une bizarrerie js, la déclaration de set et get bien que séparés, sont vraiment la même déclaration de propriété. Le contrôle d'erreur dans le compilateur est d'alerter les utilisateurs lorsqu'ils pensent à eux séparément ; les décorateurs ne sont appliqués qu'une seule fois au descripteur de propriété.

je me demandais simplement puisque le compilateur peut détecter les get et set avec le même nom et les combiner en un seul objet.defineProperty, pourquoi ne pas également combiner le décorateur?
ou peut-être un indicateur d'option pour dire au compilateur de les combiner et de laisser un message d'avertissement au lieu de lancer une erreur.

Merci

@TakoLittle : La raison pour laquelle nous ne le faisons pas aujourd'hui vient en partie de la composition des décorateurs. Les décorateurs suivent les mêmes principes que la composition de fonction mathématique, où (_f_ ∘ _g_)(_x_) est composé comme _f_(_g_(_x_)). Dans le même sens, on peut penser que :

<strong i="7">@F</strong>
<strong i="8">@G</strong>
class X {}

Est d'environ:

F(G(X))

La compositionnalité des décorateurs se décompose lorsque vous décorez à la fois le getter et le setter :

class C {
  <strong i="15">@F</strong>
  set X(value) {}

  <strong i="16">@G</strong>
  get X() {}
}

Comment F et G composent-ils ici ? Est-ce basé uniquement sur l'ordre des documents (c'est- F(G(X)) dire G(F(X)) dire get et set impliquent un ordre spécifique (c'est-à-dire que le get toujours avant le set ou vice versa) ? Jusqu'à ce que nous soyons sûrs à 100 % de l'approche la plus cohérente qui ne surprend pas les utilisateurs, ou d'une approche bien documentée faisant partie de la proposition des décorateurs avec au moins l'étape 2 ou une meilleure acceptation au sein de l'ECMA-262, nous pensons qu'il est préférable de être plus restrictif et d'erreur ici car cela nous permet d'assouplir cette restriction à une date ultérieure sans introduire un changement de rupture qui pourrait facilement passer inaperçu et éventuellement entraîner des comportements inattendus au moment de l'exécution.

@rbuckton merci beaucoup pour l'explication détaillée
L'équipe TS super travail !! ^^d

Où est la documentation pour cela? et souciez-vous de lier le commit de mise en œuvre ?

Merci.

@mhegazy Quel est l'état d'avancement de la mise en œuvre de la dernière version de la spécification. Je comprends qu'il y a des changements là-bas.

Ce numéro suivait la version originale de la proposition. puisque cela est terminé, nous fermons ce problème. pour toute mise à jour de la spécification, nous enregistrerons les nouveaux problèmes et présenterons tous les changements de rupture. Je ne pense pas que la proposition soit maintenant prête à être prête à sauter dessus. Nous travaillons en étroite collaboration avec @wycats sur la nouvelle proposition.

@omeid , vous pouvez trouver de la documentation sur https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Decorators.md

@mhegazy Merci pour la mise à jour. J'aimerais rester informé. Lorsque vous créez le nouveau problème pour la mise à jour des spécifications, veuillez le lier ici afin que je puisse être averti et suivre. La communauté Aurelia fait un usage intensif des décorateurs et nous voudrons nous synchroniser à la fois avec TypeScript et Babel lors de la mise à jour. Encore une fois, merci pour l'excellent travail que fait l'équipe TS !

La décoration de fonction est bien sûr nécessaire.
Existe-t-il également des projets de décoration d'autres objets dans le code ?

Cette page vous a été utile?
0 / 5 - 0 notes