Typescript: Decoradores

Creado en 7 mar. 2015  ·  139Comentarios  ·  Fuente: microsoft/TypeScript

Propuesta ES7

La propuesta de ES7 para decoradores se puede encontrar aquí: https://github.com/wycats/javascript-decorators
La propuesta ES7 sirve como base de esta propuesta. A continuación se muestran notas sobre cómo el sistema de tipos

Objetivos del decorador:

Constructor de clases

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

azúcares para:

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

Métodos

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

azúcares para:

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étodo estático

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

azúcares para:

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

Propiedades

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

azúcares para:

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

Método / parámetro formal de acceso

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

azúcares para:

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

Donde __decorate se define como:

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

Firmas del decorador:

Un decorador válido debe ser:

  1. Asignable a uno de los tipos de Decorador (ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator) como se describe a continuación.
  2. Devuelve un valor (en el caso de decoradores de clases y decoradores de métodos) que se puede asignar al valor decorado.
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;

Notas:

  • No se permite decorar una declaración de función, ya que bloqueará la elevación de la función a la parte superior del alcance, lo cual es un cambio significativo en la semántica.
  • No se admiten expresiones de función de decoración ni funciones de flecha. Se puede lograr el mismo efecto aplicando la función decoradora como var x = dec(function () { });
  • Los parámetros formales de la función de decoración no forman parte actualmente de la propuesta de ES7.
  • No se permiten decoradores al apuntar a ES3
Committed ES Next Fixed Suggestion

Comentario más útil

La decoración funcional es necesaria, por supuesto.
¿También hay planes para decorar otros objetos en el código?

Todos 139 comentarios

Disculpe lo que entiendo de la especificación, no podremos hacer:

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

Estoy en lo cierto?

¿Cómo funciona la serialización de tipos con argumentos rest?

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

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

El uso de decoradores parece bastante sencillo, pero las secciones sobre cómo declararlos me resultan confusas. C.4 dice que los decoradores deben anotarse con @decorator , pero ni uno solo de los ejemplos muestra que esto esté sucediendo.

¿Se pretende que las fábricas de decoradores sean clases que implementen las interfaces que se encuentran en B?

¿Cuál es la regla para refinar la interpretación de CoverMemberExpressionSquareBracketsAndComputedPropertyName?

Noté que muchos de los mecanografiados tienen Function | Object en varios puntos, pero estos degenerarán en Object en el momento de la verificación de tipos. ¿Cuál es la razón para tener Function allí?

No estoy loco por los términos DecoratorFunction vs DecoratorFactory. Prefiero seguir la nomenclatura de generadores, que tiene Generator y GeneratorFunction. Con este esquema, cambiaríamos el nombre DecoratorFunction a Decorator y DecoratorFactory a DecoratorFunction.

Para las exportaciones decoradas, ¿para qué es [lookahead ≠ @] ? ¿Pueden HoistableDeclaration y ClassDeclaration realmente comenzar con @ ?

Este es un duplicado de # 1557

No es realmente un engaño, ya que el # 1557 lo fue para un diseño diferente. Este problema es para el diseño de los decoradores que se está implementando ahora.

Mi error.

Para el decorador en la expresión de la función, ¿no podríamos hacer algo como:

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

transformado en:

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

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

Es un poco molesto para algunos tener que corregir todas las funciones como:

const myFunc = function () {}

Pierdes el izado y function.name

Implementación agregada en PR # 2399

Actualizaciones: propuesta actualizada y enlace agregado a los decoradores de JavaScript de @wycats ES7.

entristecido de que se haya convertido en una única cosa de clase ...
Además, ¿qué pasa con el decorador ambiante que se salieron del alcance?

@fdecampredon con su propuesta de funciones, parece que todavía pierde el alzamiento.

@JsonFreeman ¿ _t en la parte superior del archivo?

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

Además, incluso si mi propuesta tiene muchos problemas, me gustaría seriamente poder usar decoradores en función (incluso si tengo que usar la función asignada variable) y perder el izado.
Un caso como este parece un caso de uso de decorador bastante bueno tanto para la función como para la clase (especialmente junto con los decoradores ambientales si aún terminan en el alcance)

@fdecampredon esto no funciona para el caso general, ya que los decoradores son expresiones en sí mismos. p.ej

myFunc();  // assumes function declaration is hoisted

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

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

si eleva la declaración de función y la aplicación del decorador, rompe el decorador. si solo levanta la declaración de la función, pero no la aplicación decoradora, puede presenciar la función en un estado sin decorar. no hay soluciones atractivas aquí.

este es el mismo problema que con la cláusula de extensión de declaración de clase, que en ES6 es una expresión. el resultado no fue levantar la declaración de clase, solo el símbolo (similar a la declaración de var, pero no las declaraciones de función)

Oups no lo pensó gracias @mhegazy.
Sin embargo, por qué la parte de función ha abandonado por completo la propuesta original de @jonathandturner tenía la regla:

decorated function declarations cannot be hoisted to the containing scope

Perder el izado es sin duda un inconveniente, pero me parece dañino transformarlo en una característica de clase única cuando tendría un caso de uso para otra construcción.

Veamos qué parece implicar el conjunto deseado de restricciones:

  • la función decorada debe ser izada
  • la función debe decorarse tan pronto como esté disponible; por lo tanto, la aplicación de decorador debe levantarse
  • El decorador debe definirse antes de que se aplique; por lo tanto, la definición del decorador en sí (o todas las entidades a las que hace referencia la expresión del decorador) deben ser elevadas.
  • La expresión del decorador se evalúa en el lugar donde se aplica léxicamente, por lo tanto, la aplicación no se puede elevar.

La única resolución que puedo ver para esto es la siguiente: Para los decoradores de funciones, solo permitimos algo de la forma @identifier . No permitimos una expresión del lado izquierdo. Además, el identificador debe ser una referencia directa a una declaración de función (incluida una función decorada). Todas las decoraciones de funciones que tienen lugar en el alcance deben emitirse en la parte superior del alcance, en el orden en que se aplicaron.

El problema de romper las reglas de izado es que es sorprendente. Si está escribiendo javascript durante un tiempo, espera que las declaraciones de funciones estén disponibles antes de que se declaren léxicamente; ahora, al agregar un marcador sintáctico aparentemente simple (el decorador), esta naturaleza fundamental de la declaración de función se altera.

Dicho esto, la propuesta de ES7 aún se encuentra en sus etapas iniciales, por lo que espero que evolucione y se expanda; por lo que es concebible que la propuesta final incluya funciones de alguna forma.

Creo que deberían ser izados. Estoy diciendo que las únicas decoraciones que permitimos en las funciones son las decoraciones que definitivamente están izadas. Es decir, identificadores que hacen referencia a declaraciones de funciones.

Pero hay otro problema aquí. De hecho, es imposible izar simultáneamente todas las funciones decoradas y asegurarse de que no se observen en su estado sin decorar. El problema se puede ver con un ciclo de decorador.

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

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

Incluso si se izan ambas funciones, ¿cuál se decora primero? Si dec2 se decora primero, dec1 no estará decorado para cuando se aplique a dec2.

Entonces tendríamos que elegir entre lo siguiente:

  • Las funciones no están izadas
  • Las funciones no decoradas se elevan, pero las aplicaciones de decorador no se elevan con ellas.

Si bien no me gusta ninguno de estos, ni siquiera creo que sea posible otra cosa.

Esta es la propuesta de JS. por lo que el motor no sabría si la expresión se refiere a una declaración de función o no, sin embargo, con el análisis estático podríamos saberlo. considera esto:

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

dec2 = undefined;

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

¡Puaj! Javascript ya no se vuelve más simple. :-)

Sería más fácil habilitarlo solo en la función _expressions_:

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

Las expresiones de función o lambda no se izan.

¿Es posible tener parámetros como este en un decorador en mecanografiado 1.5.0-alpha?

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

Ok no importa, solo crea una fábrica que toma los parámetros y devuelve la función de decorador real.

Por ejemplo, un decorador de clases con un parámetro de cadena:

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

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

}

saludos.

Estoy tratando de averiguar cómo escribir un decorador que recoja los tipos declarados en el constructor.

aquí hay un código que ilustra lo que estoy tratando de hacer, pero está programado. lo necesito para responder a la declaración del constructor en la clase 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;
}

Me temo que la información de tipo está fuera del alcance de la función de decorador.
Puede usar ParameterDecorator en cada parámetro para agregar ese tipo de información.

La mecanografía o implementación de ParameterDecorator no es del todo correcta, a partir de las mecanografías el objetivo es una función, pero mientras se usa mecanografiado, es el objeto prototipo.
Debo lanzar el parámetro de destino para obtener el tipo correcto:

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

En lugar de:

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

wow - regla de decoradores de parámetros !!!!!!!!!!!!!!! ver este repositorio

aquí está el resultado de ejecutar las pruebas:

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

y el código :

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

Hice una envoltura alrededor de Express (pero se podría admitir cualquier marco web, se define una interfaz de adaptador) con una API basada en decoradores: https://github.com/cybrown/web-decorators

Estoy usando ClassDecorator, ParameterDecorator y MethodDecorator.

@cybrown Acabo de actualizar la firma para ParameterDecorator en # 2635, que ahora está en master.

@rbuckton Gracias, lo actualizaré mañana en mi proyecto.

¿Es posible tener el nombre del parámetro en un ParameterDecorator?
Esto puede ser útil porque creo que el nombre del parámetro podría ser la mitad de las veces el primer argumento de un ParameterDecorator.
Además, podría ser una solución para la manipulación de nombres de parámetros por parte de los minificadores.

Me han pedido que logre esto:

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

pero sin especificar las claves en el decorador de inyección, sino que los decoradores seleccionan las funciones de clase del constructor automáticamente con algo como esto:

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

Estoy buscando cómo hacer lo que @jonathandturner describe aquí

@cmichaelgraham Sí, tener información de tipo de tiempo de ejecución (rtti como se describe en lo que era AtScript) sería increíble para ese tipo de uso, incluso en una función completamente separada como la introspección.

@cmichaelgraham Estamos trabajando en una propuesta para que ES7 agregue una API de metadatos que funcionará junto con los decoradores. # 2589 agrega soporte experimental para esa propuesta, pero requiere que haya un polyfill separado para leer los metadatos.

@rbuckton @cmichaelgraham Originalmente había un diseño para un decorador de TypeScript especial que le decía al compilador que inyectara tipos en el decorador, según el objetivo que se decoraba. Creo que fue @parameterTypes ¿Estoy en lo correcto al pensar que esa característica se está eliminando a favor de la API de metadatos más general? Si es así, lo apoyaría.

¿Puedes hablar un poco sobre el estado de eso con respecto a TypeScript? ¿Está planeado para el lanzamiento de 1.5? Si es así, ¿cómo serán las opciones del compilador? Una cosa que sería útil es que el compilador genere automáticamente los metadatos de tipo solo para las firmas del constructor.

@EisenbergEffect Decorators son parte del 1.5, puede comenzar a usarlo con nuestra última versión 1.5-alpha .

En cuanto al soporte de metadatos. El diseño ha cambiado desde la propuesta original de @paramtypes . el nuevo diseño está utilizando la propuesta Reflect.metada. Consulte el n. ° 2589 para obtener más detalles sobre cómo funciona. también @rbuckton tiene un pollyfill para consumir los metadatos aquí: https://github.com/rbuckton/reflectDecorators

@mhegazy Lo sé. Ver aquí: http://blog.durandal.io/2015/04/09/aurelia-update-with-decorators-ie9-and-more/ : smile:

También estoy familiarizado con la propuesta de metadatos. He estado proporcionando comentarios sobre eso. Solo quería saber si se estaba eliminando la idea @parameterTypes .

gracias @EisenbergEffect por compartir. : +1:

Si. el único problema con @paramtypes es que hace que la emisión sea dirigida por el sistema de tipos y requiera información global del programa. Esto rompe los escenarios para la transpilación de un solo módulo (ver # 2499). La otra opción era ponerlo en sitios de llamadas, lo que agrega mucho trabajo a los usuarios decoradores en lugar de a los autores. Así que volvimos a la mesa de dibujo y aterrizamos en el enfoque Reflect.metadata en su lugar.

Si también miró versiones anteriores de la propuesta, tuvimos que eliminar los decoradores de ambiente / tiempo de diseño por la misma razón.

Gracias por aclararlo. Todo eso tiene sentido. ¿Alguna idea de si el enfoque basado en Reflect aterrizará en 1.5?

si. actualmente se encuentra en master. debería estar disponible en la próxima versión. actualmente es una función opcional que utiliza la marca experimental --emitDecoratorMetadata ; y solo agrega metadatos a entidades decoradas.

"solo agrega metadatos a las entidades decoradas" ¿Puedes ampliar esa idea? ¿Está involucrado un decorador especial? ¿O lo agrega a cualquier cosa con cualquier decorador? En otras palabras, si un desarrollador usa el decorador inject Aurelia, ¿activará eso el compilador para generar los metadatos?

si un desarrollador usa el decorador de inyección de Aurelia, ¿activará eso el compilador para generar los metadatos?

si.

lo que quise decir es:

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

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

con --emitDecoratorMetadata el compilador emitirá metadatos de tipo (es decir, llamar a reflect.metadata('desing:paramtypes', [Number, String]) ) por Foo pero _no_ Bar .

Eso funcionará para nosotros. Prepararemos el soporte para la API Reflect.metadata para nuestro próximo lanzamiento, probablemente la semana que viene. ¡Gracias por la aclaración!

entonces, ¿qué se emite por esto?

`` `idioma = javascript
@inyectar
class Foo {
constructor (a: A, b: B) {}
}

class Bar {
constructor (a: número, b: B) {}
}

would it be (for Foo):

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

@cmichaelgraham Sí, eso es aproximadamente lo que se genera.

loco genial !!!!

usando gulp-typescript para construir este repositorio - (buen trabajo @ivogabe)

gulpfile build-ts comando (tenga en cuenta la opción 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('.'))
    ]);
});

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

de especial interés son los metadatos de tipo emitido sobre los parámetros del constructor:

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

entonces, en teoría, podría alterar la función de decorador de inyección para inyectar las clases adecuadas sin especificarlas en la inyección, sino especificando los tipos en el constructor :)

@cmichaelgraham Podemos habilitar esto fácilmente hoy sin alterar el marco. La biblioteca DI de Aurelia tiene un gancho container.addParameterInfoLocator . Le pasa una función que puede tomar un tipo y devolver sus dependencias. Sin embargo, para nuestro próximo lanzamiento (la próxima semana), podemos agregar esto a la configuración principal para que sea fácil para los desarrolladores de TypeScript activarlo. Lo habría puesto en el lanzamiento de esta semana, pero no me había dado cuenta de que esto había cambiado todavía.

@EisenbergEffect brillante !! : +1:

Usé anotaciones para marcar las propiedades de un objeto como observables, lo que transforma una primitiva en un objeto observable (para aquellos interesados, https://github.com/mweststrate/MOBservable/commit/8cc7fc0e20c000db660037c8b5c9d944fe4155d9).

Sin embargo, especialmente para las propiedades, se sintió un poco antinatural que las anotaciones se aplicaran al prototipo, y no en el constructor de una clase en sí, esto significa que es difícil obtener el valor inicial o incluso el this . La solución para eso fue crear una propiedad, y en su getter / setter realizar la lógica real que se suponía que debía hacer la anotación, ya que this y el valor están disponibles en ese momento.

Además de eso, ¡una característica increíble!

Usando la API de reflexión de metadatos, pude hacer una inyección de dependencia de propiedades simple.
Puede verificar los cambios requeridos aquí https://github.com/matjaz/property-DI/commit/2b4835e100b72d954b57d0e656ea524539ac17eb.

¿No debería ser el decorador de __metadatos en el código generado invocado en primer lugar los decoradores? Quería crear un deocrator simple usando los metadatos de la clase, pero __metadata ('design: paramtypes', [TYPES ....]) se invoca como último, por lo que no puedo obtener estos datos de Reflect

@ufon ¿puedes compartir el código que estás viendo?

Claro, aquí https://gist.github.com/ufon/5a2fa2481ac412117532

EDITAR:
mi mal, hay algún otro error en mi código, no puedo obtener tipos incluso después de la inicialización de la clase

@ufon , entonces no estoy seguro de haber visto el problema. __metadata es el último elemento en la lista de decoradores, esto significa que es el primero en ejecutarse, y definitivamente antes de que la inyección llegue a ejecutarse.

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

donde los usos de la definición __decorate reducen el derecho para ejecutar los decoradores en orden inverso.

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

Esto es para coincidir con la especificación, que los decoradores (como expresiones) se evalúan en el orden de declaración, pero se ejecutan en el orden inverso, lo que permite que los decoradores externos consuman los resultados de los decoradores internos.

Ya veo, lo siento por molestar :) mi mal, hay algún otro error en mi código, no puedo obtener tipos incluso después de la inicialización de la clase

¿Cómo las buscas? y ¿tiene un pollyfill para Reflect.metadata?

añadido a la esencia ...
Reflect.getMetadataKeys (Casa);
esto da como resultado una matriz vacía.

Sí, necesito el paquete 'reflect-metadata'

parece que el polyfill lo pierde, @rbuckton ,

Creo que lo tengo ...
Los requisitos se mueven después del código de ayuda mecanografiado.

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

Entonces, en el tiempo que var __metadata se inicializa, polyfill aún no se ha cargado

pero no puedo obtener estos requisitos antes del código generado

EDITAR: actualizado la esencia ... cuando se compila un solo módulo, no hay forma de cargar polyfill antes de que se resuelva __metadata, porque el código generado mecanografiado siempre se mueve a la parte superior del archivo

ooh .. este es un problema mayor. problema registrado # 2811 para rastrearlo

Hasta entonces, necesita reflect-metadata en otro módulo (necesita dos archivos).
ver https://github.com/matjaz/property-DI/

Parece haber una incoherencia de invocación que dificulta el desarrollo de decoradores con parámetros variables / opcionales. Como ejemplo:

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

@F()
prop: number;

En el primero, F se invoca con los parámetros (target, propertyName, propertyDescriptor) mientras que en el segundo, F se invoca con los parámetros () y requiere que se devuelva una función interna que a su vez se invoca con (target, propertyName y propertyDescriptor).

Mi _ suposición_ habría sido que tanto @F como @F () serían equivalentes y, por lo tanto, solo puede realizar cualquiera de las llamadas si el decorador admite 0 o más parámetros.

Esto es particularmente importante en el caso:

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

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

Esto se puede solucionar simplemente llamando explícitamente a @F () en lugar de @F, pero es fácil cometer ese error y luego confundirse cuando su decorador no funciona aunque, por lo que parece, debería.

En este caso, me gustaría hacer lo siguiente:

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

y terminar con eso, pero en su lugar tengo que hacer algo como el siguiente ejemplo crudo:

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

Lo que es un verdadero dolor.

@Tharaxis este comportamiento es por diseño. @F y @F() no son equivalentes. @F llama a un decorador F , mientras que @F() llama a un factor de decorador F con una lista de argumentos vacía, y luego aplica el decorador resultante.

El problema aquí es que el compilador compara la firma de la fábrica decoradora F con declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; que termina siendo asignable. Creo que necesitamos un control más sólido aquí para asegurarnos de que detectamos este problema. He registrado # 3246 para arreglar esto. Creo que está haciendo lo correcto y el compilador debería detectar los usos no válidos de una fábrica de decoradores.

@mhegazy No estoy seguro de por qué este es el caso, sin embargo, ¿hay algún caso de uso que impida que uno haga @F y @F() equivalentes, ya que en última instancia ambos son una invocación idéntica de la función decoradora? , ¿solo uno de ellos incluye también una invocación a una función de fábrica externa? Parece que aquí se está violando el principio del menor asombro, ya que este es definitivamente un comportamiento bastante sorprendente y no veo por qué tiene que haber 2 formas de invocar a un decorador desde la perspectiva del consumidor de API.

Si bien entiendo que desde una perspectiva de JS hay una diferencia (en uno está pasando la referencia de la función F y en el otro está ejecutando una función y luego pasando la referencia de su resultado como decorador), no estoy Estoy seguro de que entiendo por qué uno no puede tener decoradores que se pasen como @F que coincidan implícitamente (e invoquen) a la fábrica como uno con una lista de parámetros vacía.

El compilador no tiene forma de saber si es una fábrica o un decorador. si tiene esta firma:

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

¿Es esto un decorador o es una fábrica, debería llamarse con argumentos vacíos o no?

Lo máximo que puede hacer el compilador es verificar que la firma coincida al llamarlo de una forma u otra, y dar errores si no lo hace.

@rbuckton tiene una solución para # 3246 que debería marcar los casos en los que su decorador no se usa como fábrica, así que estad atentos.

@mhegazy , lo que estoy diciendo es que el uso de un decorador (es decir, @F o @F() ) debería _siempre_ ser una invocación de la función de fábrica F, que a su vez devuelve el tipo de decorador apropiado. No existe una representación de alto nivel de un decorador sin una fábrica de encapsulación.

En otras palabras, la firma de F es siempre:

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

O algún equivalente.

Entonces la invocación @F se convierte en el equivalente de @F() por el compilador. Dado que tanto la firma de @F como la de @F() deben coincidir con la firma de fábrica de alguna forma, esto resuelve el problema de la confusión de invocación. ¡Solo hay una forma de invocar a un decorador!

Dado lo siguiente:

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

@F()
property: number;

y

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

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

¡Hacen lo mismo pero tengo que declararlos de manera diferente! Sin embargo, declarar F para que exista en ambos sentidos es realmente engorroso y hace que la creación de documentación y parámetros consistentes sea imposible. O está eligiendo que su decorador necesite usar una fábrica o no, y su consumidor de API debe conocer esta distinción de antemano para hacer uso de ella.

@Tharaxis Si estoy viendo una biblioteca que proporciona F que se usará como decorador, espero absolutamente que @F y @F() sean cosas diferentes. Al igual que esperaría que C y C() sean cosas diferentes: el primero hace referencia a un valor, el segundo invoca un valor. @F aplica un decorador, @F() invoca una función que creará un decorador. Esas no son, ni deberían ser, la misma operación.

@DavidSouther, ¿hay alguna razón válida por la que esto no pueda o no deba ser el caso? No hay una diferencia funcional entre una invocación de @F y @F() pero hay una diferencia bastante grande entre cómo se definen y documentan, y agrega complejidad adicional a la invocación que debería ser innecesaria.

Mi propuesta es que no debería haber dos formas de definir un decorador porque no hay necesidad, y no debería haber necesidad de definir un decorador de más de una forma.

No hay diferencia funcional entre una invocación de @F y @F ()

Creo que este es el principal punto de discordia. No creo que esto sea cierto. F es una referencia a una función, F () es una referencia al valor de retorno de F cuando se llama con un conjunto de argumentos vacío (es decir, F.call(this, []) ); y estas son dos cosas funcional y conceptualmente diferentes.

@Tharaxis @F y @F() son cosas diferentes. Hay una diferencia en cómo se usan, una diferencia en cómo se documentan y diferencias en su invocación que son absolutamente necesarias.

Permítanme preguntarlo de otra manera: ¿por qué cada función del decorador debe ser una fábrica? Usando tu propuesta, sería imposible tener un decorador simple.

@mhegazy, aunque @F() ) da como resultado la generación de un cierre funcional y una sobrecarga adicional a través de la creación de funciones de decorador aisladas (y duplicadas) y la invocación @F esencialmente funciona contra una referencia de función de decorador compartida, yo diría que el aislamiento a través del cierre sería "más seguro" y más consistente, reduciendo la probabilidad de sorpresas.

@DavidSouther Tu pregunta depende de lo que

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

Encuentro esto bastante mínimamente invasivo ya que es solo 2 líneas más que la "sintaxis simple" y define de manera más obvia cuáles son las invocaciones de decorador disponibles. Además, dado que solo hay una forma única de definir un decorador (y no descartemos el valor de la coherencia), probablemente le tomará aproximadamente otros 2 segundos para aclarar esto en comparación con la "sintaxis simple".

Además, un problema más problemático es el siguiente:

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;

Una de estas cosas no es como la otra. El uso de @F no funcionará aunque a valor nominal definitivamente debería hacerlo. Tenga en cuenta, finja que soy alguien que no es consciente de cómo se ve la declaración subyacente de F, pero en su lugar solo sabe que F existe y que puede tomar un conjunto opcional de argumentos. Las posibilidades de que me equivoque con @F no son triviales con el mecanismo actual. Esto impone una gran responsabilidad al desarrollador / documentalista para asegurarse de que el consumidor sepa que @F no funcionará (pero tal vez @C sí porque es "diferente" de alguna manera).

Si quiero un decorador de "talla única", donde el consumidor no tenga que preocuparse, tengo que hacer esta cosa espantosa:

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

Lo cual es francamente horrendo. Además, pierdo todo el intellisense de parámetros valiosos contra el decorador F, ya que ahora tiene que generalizarse.

Hay mucho que decir acerca de que los decoradores sean fáciles y coherentes de definir y documentar. No nos engañemos pensando que todos los que consuman nuestro código tendrán el mismo conocimiento o comprensión que nosotros, que somos los creadores de ese código.

@Tharaxis Pensé en esto al principio del diseño para decoradores y entiendo tus puntos específicos. Como @mhegazy y @DavidSouther mencionan anteriormente en este hilo, este comportamiento es consistente con el comportamiento estándar de las funciones en JavaScript.

Tiene razón en que cualquier intento de crear una función que pueda actuar tanto como decorador como decorador de fábrica puede resultar algo complejo. Sin embargo, en general, puede ser mejor emplear una mejor práctica para usar siempre las fábricas de decoradores y proporcionar reglas a un linter para garantizar que se adopte esta práctica.

Además, acabo de enviar PR # 3249 para abordar su punto anterior con respecto a las inconsistencias en la verificación de tipos para decoradores.

Hola @rbuckton ,

Soy uno de esos usuarios no tan inteligentes de decoradores mencionados por @Tharaxis ...

Supongo que la pregunta que tendría es si los decoradores deben ser consistentes con las funciones. Es decir, en el caso de las funciones, devolver this.f versus this.f () tiene mucho sentido para mí, ya que a veces quiero el valor, a veces quiero lo que produce el valor.

En el caso de los decoradores, no me parece tan claro a nivel de características lingüísticas. Me gustaría argumentar que solo quiero aplicar el decorador @F , realmente no quiero saber si se implementó como un método de fábrica o estático. Eso es a menos que pierda alguna capacidad que los decoradores tendrían de otra manera.

Mis disculpas si lo anterior está mal informado o es ignorante, soy relativamente nuevo en el mundo de javascript.

Gracias y saludos

También tengo que hacer esa pregunta: ¿tiene sentido mantener la paridad sintáctica con las funciones _ si_ (y solo si) no hay ninguna razón real para hacerlo que no sea "porque se define mediante funciones". Veo a los decoradores como una característica completamente ajena a las funciones que simplemente _sucede_ ser definidas usándolas.

En este momento, el hecho de que @F y @F() no sean idénticos provoca los siguientes problemas:

  • Los desarrolladores que necesitan comprender la diferencia entre @F y @F() y por qué a veces esto puede funcionar y por qué a veces esto puede no funcionar (y la necesidad de usar @F frente a @F() podría ser completamente específico del marco / código / uso, por lo tanto, inconsistente desde el punto de vista del consumo: Imagine declare function A(params?: any): ParameterDecorator vs declare function B(target, name) , @A no funcionará, pero @A() , @B funcionará pero @B() no lo hará, pero cognitivamente son lo mismo, una aplicación de un decorador sin argumentos).
  • Resolver el problema desde un nivel de código requiere una solución complicada que elimina la capacidad de documentar adecuadamente la sintaxis de invocación del Decorador.
  • Refactorizar es un poco más complicado si ha creado un 'decorador sin formato' ( @F ) y ahora desea agregar algunos parámetros (pero hágalos opcionales para compatibilidad con versiones anteriores). Con el método actual, todos esos @F aplican ahora deberán refactorizarse a @F() , algunos dentro del código al que es posible que no tenga acceso, mientras que la invocación implícita le permitiría mantener las cosas funcionando y restringir el cambie a su código.

@Tharaxis Entiendo que la distinción entre @F y @F() puede ser una gran carga cognitiva para los consumidores. Sin embargo, parece que está pidiendo que cambie el código emitido y, por lo tanto, el comportamiento, de modo que los dos se comporten de forma equivalente. Esto probablemente entraría en conflicto con la propuesta de ES7 para decoradores, ya que es muy poco probable que las dos formas se traten de la misma manera. No será bueno si esto conduce a una divergencia.

Otro punto es que la confusión aquí es similar a la confusión entre pasar una función como una devolución de llamada y llamar a la función. Si tuvieras esto

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

Un usuario puede confundirse y llamar a takesCallback(callbackFactory) sin invocar callbackFactory . Esto es realmente como la confusión que señaló con los decoradores, pero si el lenguaje normalizara estas dos formas para hacer lo mismo, habría una pérdida de expresividad. A veces, la distinción es importante y debe mantenerse. No estoy seguro de si esto también se aplica comúnmente a los decoradores, pero en teoría ciertamente podría hacerlo.

@JsonFreeman Sin embargo , realmente no entiendo tu argumento. Estás diciendo que no puedes hacer que los decoradores tengan sentido como su propia entidad porque existe una propuesta de ES7 para decoradores, pero esa es la cuestión, los decoradores de ES7 son solo eso, una _propuesta_, y hasta donde yo sé, ellos ' se basan en parte en una combinación del trabajo del decorador descrito tanto por Google como por ustedes mismos, ¿correcto? Aún no se ha decidido cómo van a funcionar, y es probable que la propuesta pase por _muchas_ iteraciones antes de la aprobación, así que ¿por qué no hacer que funcionen de manera consistente en TS y luego (al menos intentarlo) obtener las distintas partes involucradas en la propuesta detrás de la idea, tomando como base la experiencia adquirida en TS? Esta no sería la primera vez que TS implementa una función solo para tener que refactorizar más tarde porque la especificación ES6 requería un comportamiento ligeramente diferente. _Siempre_ tendrá ese problema siempre que realice un seguimiento de una especificación que aún no existe y que aún no ha sido acordada.

En cuanto a su ejemplo con respecto a las funciones, parece que una vez más estamos fusionando funciones con decoradores cuando en realidad son (y deberían ser) construcciones completamente diferentes. Cuando trabajo con una función en JavaScript, entiendo cómo se usan las funciones y entiendo la diferencia entre una referencia de función y una llamada a función (y cuándo podría querer usar una en comparación con la otra), y cuando describo algo como función, no hay confusión en cuanto a los usos que tiene. Los decoradores no son funciones y no deben heredar el comportamiento de las funciones. Las funciones son simplemente el método sobre el que se construyen los decoradores. Eso es como decir que las clases son funciones, lo cual no es el caso, las funciones son simplemente la forma en que describimos las clases antes de ES6 porque no había un método más obvio disponible. Las clases no toman las propiedades de las funciones, no puede llamar a clases como funciones, pero las declara usando funciones anteriores a ES6.

Independientemente, siento que he agotado mis puntos de discusión sobre el tema. Tendré que solucionar la elección. En mi propio código, siempre haré uso de las fábricas por el bien de mi propia consistencia. Sigo creyendo que tratar a los decoradores literalmente como funciones es probable que cause mucha frustración en el futuro, pero así soy yo.

@mhegazy Este es el mismo problema que mencioné en el @jonathandturner aquí: https://github.com/jonathandturner/decorators/issues/16 Creo que esto es algo en lo que se debe pensar. Tal como está, espero que esta elección de diseño sea el tema de muchas publicaciones de blog tituladas algo similar también "ES7 Decorator Pitfalls".

@Tharaxis , estoy de acuerdo con tu primer punto. No creo que una propuesta de ES7 deba impedirnos diseñar algo bien, y nuestro diseño es de hecho uno de los precursores de ES7 de todos modos.

En cuanto al argumento de que "los decoradores no son funciones", si no me equivoco, no estamos hablando de los decoradores en sí, sino de las fábricas de decoradores. E independientemente de cualquier distinción entre decoradores y funciones, creo que las _factorías_ de decoradores son en realidad solo funciones. Y parece que está pidiendo que el compilador genere automáticamente una llamada a una fábrica de decoradores, que es una función normal. Si eso sucediera, el programador no tendría forma de distinguir entre la fábrica decoradora y la decoradora.

Además, lo bueno de tratar a los decoradores como funciones es que un consumidor puede aplicarlo como decorador o simplemente llamándolo como mejor le parezca.

@JsonFreeman el argumento gira en torno a la situación actual en la que hay dos formas de describir e invocar a un decorador, una a través de una función de fábrica y otra como una función "sin procesar" (que en el caso de fábrica es el resultado de una llamada de fábrica ), y la pregunta era más sobre "¿no debería haber un solo camino, y que las fábricas fueran así?".

Lo que estoy preguntando es sí, ¿no podríamos simplemente hacer que el compilador convierta @F en el equivalente de la llamada @F() y que la verificación de tipos requiera una lista de argumentos de parámetros 0 ... n cuando se usa el sintaxis sin corchetes. Quizás podría explicar en detalle lo que significa "... no habría forma de que el programador distinga entre la fábrica de decoradores y el decorador ...", ya que creo que es muy fácil de distinguir. El decorador es siempre la respuesta de la fábrica, y el nombre de la fábrica es el nombre del decorador ... no es tan difícil, ¿o lo estoy entendiendo mal?

En cuanto a su último punto sobre permitir que el consumidor simplemente aplique el decorador, si se describe bien que todos los decoradores usan fábricas, es bastante fácil invocar a un decorador usted mismo, simplemente haga <decorator name>(<argument list>)(target, name) comparación con <decorator name>(target, name) como ejemplo. Tenga en cuenta que exigir el uso de fábricas significaría que el ejemplo anterior siempre funcionará, mientras que no hacerlo dará como resultado que el último ejemplo a veces no funcione, y depende completamente de cómo se implemente el decorador: un dolor de cabeza que está esperando a suceder.

Siento que es necesario señalar que no tengo ningún problema con que los decoradores usen funciones, mi problema es, sin embargo, que tener dos formas de describir lo mismo conduce a problemas de coherencia. Especialmente cuando esas dos formas también significan que su método de invocación debe ser diferente. Esta es una disparidad que dificulta todo, desde la refactorización hasta la coherencia del lenguaje.

El problema de refactorización que describí en algunas publicaciones ya debería ser una razón más que suficiente por la que el método actual debe ser inspeccionado.

Hola a todos, solo un valor final de 1.5 centavos de un usuario regular en la línea de lo que ha dicho @Tharaxis .

Estaría más que feliz con la propuesta actual si:
a) el universo lo ordena.
b) los decoradores perderían capacidades importantes si las cosas fueran diferentes.

Lo último es, por supuesto, un juicio de valor, cuya respuesta dependerá en cierto grado del tipo de desarrollador que sea (es decir, usuario general / usuario experto, etc.). Estoy en la primera categoría y generalmente estoy disperso en varios lenguajes y marcos. Entonces, para mí, las 'capacidades importantes' no incluirán la flexibilidad para escribir decoradores de 2 maneras diferentes. Los ejemplos de todas las compensaciones serían excelentes si existieran.

Idealmente, sería genial si @F y @F () pudieran ser consistentes independientemente de cómo se implementen los decoradores, pero si no, preferiría estar limitado a usar fábricas al escribir decoradores en lugar de tener que evitar rastrillos en el césped cada vez que los estoy usando.

Gracias y saludos

Parece que esta solicitud depende de la idea de que las fábricas de decoradores son la forma canónica de usar decoradores, pero no veo cómo esto permite al usuario definir y usar decoradores en bruto. Si defino un decorador F , y la aplicación @F se trata como @F() , entonces el resultado de llamar a F se usa como decorador, en lugar de F en sí. ¿Está sugiriendo que demos un error en alguna parte si alguien intenta aplicar un decorador en bruto en lugar de una fábrica de decoradores?

Esta idea parece revertir la composicionalidad natural de los decoradores y las fábricas de decoradores. Los decoradores definitivamente se sienten como la construcción primitiva aquí, con las fábricas de decoradores como una capa de abstracción construida sobre los decoradores. Son solo un patrón, nada más. Si, en cambio, la fábrica de decoradores se convirtiera en lo canónico, lo primitivo, entonces la gente definiría un grupo de fábricas de decoradores, que no aceptan argumentos y devuelven un decorador plano. Esto comenzaría a parecer muy tonto y revertiría la intuición natural de lo que se considera básico y lo que se considera más complejo.

Una cosa que desconfío mucho de los decoradores es el exceso de magia. Personalmente me pongo nervioso cuando no entiendo qué está haciendo el código, y si el compilador está agregando secretamente invocaciones adicionales que el programador no escribió, eso me parece demasiado vudú.

Hola @JsonFreeman ,

Como mencioné, mi preferencia siempre será que la fealdad sea con el autor del decorador más que con el usuario. Sin embargo, estoy de acuerdo en que muchas fábricas sin arg son muy feas. ¿Se podría utilizar un decorador para solucionar este problema? P.ej

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

De todos modos, si se pudiera encontrar que tal enfoque tiene sentido, tiene la ventaja de que la anotación documenta el propósito de la función. es decir, decora.

Salud

Buena salsa, decoradores para ayudar a definir decoradores, un caso grave de Typeception.

@JsonFreeman No estoy seguro de que tener funciones de cero parámetros sea necesariamente más feo que tener una multitud de funciones (objetivo, nombre) en su lugar. En todo caso, al menos las funciones de cero parámetros proporcionan un nivel de especificidad / explícita del que carece el otro método (ya que los parámetros nunca coinciden con la invocación en el último caso), y además de eso, proporciona un único objetivo para documentar. de un nivel innecesario de inconsistencia. También veo una renuencia a seguir cualquier ruta debido a una complejidad "posible" percibida, pero yo diría que a la luz de la complejidad "obvia" muy real que impone la implementación actual en el lado del consumo, uno debería errar más en el lado de lo obvio que de lo posible.

La otra opción es ordenar que los decoradores que se llaman como @F puedan coincidir con el patrón para ClassDecorator, MethodDecorator, PropertyDecorator o ParameterDecorator, O una función de fábrica 0..n arg que devuelve ClassDecorator, MethodDecorator, PropertyDecorator o ParameterDecorator. Sin embargo, creo que esa implementación causaría otros errores (¿qué sucede si tiene dos funciones en conflicto, cuál se consideraría la mejor coincidencia posible?) Y solo agregaría una complejidad indebida dentro del compilador. La simple conversión de las llamadas @F a @F() sería la solución más sencilla y resolvería los diversos problemas indicados.

Lo siento, para aclarar, no estaba afirmando que su solución fuera compleja. Quise decir que invocar automáticamente al decorador antes de aplicarlo es opaco. La invocación insertada es invisible para el usuario y creo que muchos usuarios no la esperarán.

Creo que la situación actual tampoco es compleja. Como usted señala, permite a los autores de bibliotecas ser vagos o imprecisos acerca de si su función debe aplicarse como decorador. Eso te lo concederé. Pero una buena biblioteca lo dejará claro.

En el esquema que sugieres, donde haces la invocación automáticamente, ¿qué sucede cuando usas una expresión decoradora arbitraria en lugar de solo un identificador? ¿Eso también se invoca?

Estoy de acuerdo en que, a menos que se documente lo contrario, es probable que la invocación implícita sea sorprendente, pero solo para aquellos que no han usado conceptos como atributos de C # y similares.

¿Podrías explicar lo que quieres decir con una expresión de decorador arbitraria?

Buena salsa, decoradores para ayudar a definir decoradores, un caso grave de Typeception.

Llamada justa. Me sirve bien para un comentario mal pensado a última hora de una tarde de fin de semana. Lección aprendida. Internet.undo ().

Mi punto es que si resulta que la sintaxis consistente del sitio de llamadas exige el uso de fábricas, entonces estoy más que contento con eso. Sin duda escribiré a un decorador para que retire la placa de la caldera. Una vez más, preferiría un poco de dolor al escribir al decorador sobre el dolor repetido al usarlos (aunque sea un dolor potencial en este punto). Otros no estarán de acuerdo.

¿No existe también el problema con la mejora de la API? Un decorador creado sin una fábrica no puede tener parámetros opcionales agregados en un momento posterior, por lo tanto, predigo @F y @F2() en mi futuro.

Tampoco veo que obtenga este escenario al usar f y f() , son casos de uso diferentes en el lado del consumo. En el caso del decorador, siempre estoy aplicando / invocando al decorador en el objetivo en ese mismo momento, y siempre hay una invocación de método detrás de escena.

Pero el quid de esto para mí es el problema de la usabilidad, cuando estoy solicitando un decorador, no quiero tener que buscar en Google cómo lo implementó el autor; me interesa el comportamiento, no el empaque.

Salud

Solo una nota final para resumir y luego me callaré. En resumen, esto es solo una cuestión de diseño de interacción para mí. Tengo muchas ganas de ver decoradores diseñados de afuera hacia adentro y no al revés.

Como usuario de UI / UX, veo esto con bastante frecuencia. He trabajado con desarrolladores talentosos que han sugerido soluciones de interfaz de usuario que perjudicarían a los usuarios (un ejemplo fueron dos botones de guardado en un caso para resolver la complejidad de una transacción, de nuevo un tipo inteligente y un buen ser humano). Creo que cuando estás metido hasta las rodillas en la lógica de los detalles de implementación complejos, puede ser difícil olvidar lo que sabes y verlo a través de los ojos del usuario promedio.

Ahora bien, si como promedio estoy simplemente equivocado, o si la complejidad exige el diseño actual, entonces todo bien, tendré que concentrarme y aprender.

Salud

Si su decorador no es un identificador, sino alguna otra expresión, ¿lo invocaría automáticamente? Por ejemplo, digamos que es un decorador. OR-ed con una expresión de función de reserva. Algo como:

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

¿Entonces lo invoca automáticamente? Eso le daría un resultado incorrecto porque termina invocando el decorador predeterminado antes de que se aplique:

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

Esto no parece correcto.

@JsonFreeman , ¿la sintaxis del decorador permite expresiones arbitrarias como esa? No estoy 100% seguro de que permitir a los desarrolladores tanta cuerda con la que colgarse sea necesariamente una buena idea, pero soy solo yo (simplemente porque no veo expresiones arbitrarias como esa que resuelven el problema de reutilización / repetición que los decoradores pretenden resolver y en su lugar solo sirven para hacer que el código se vea feo e incluso más difícil de seguir).

Dicho esto, supongo que la única forma en que funcionaría sería si la expresión arbitraria en sí misma evaluara la misma sintaxis de fábrica, entonces:

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

Estoy de acuerdo en que tiene que poner algunos bits más complicados alrededor de su decorador arbitrario, pero creo que uno tiene mayores problemas que la adición de () => si está utilizando expresiones arbitrarias como decoradores.

NOTA: Obviamente no quise decir que la sintaxis lambda sería la única forma de declararlo, pero () => es un poco más agradable que function() { return function(target) { /*...*/ } } .

Realmente lamento interrumpir este debate de sintaxis de llamada de decorador, pero ¿alguien podría aclarar oficialmente el orden en que se llaman las expresiones de decorador? Especialmente, a lo largo de las líneas del tipo de objetivo del decorador, la posición del miembro objetivo en la fuente original y el orden de los decoradores en un solo objetivo.

@billccn Los decoradores se aplican de abajo hacia arriba. Entonces, en el código fuente original, si tuvieras

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

Esto primero aplicaría G al método y luego aplicaría F al resultado. ¿Es esto lo que estás preguntando?

¿Qué tal esto?

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

siguen el orden de alcance, por lo que todas las propiedades / métodos primero, en orden de declaración, luego la clase uno. es decir, B, C, D, E, F, A.

puedes ver el código generado aquí .

Parece que actualmente no es posible decorar las propiedades de los parámetros. ¿Pueden recibir apoyo?

Las propiedades de los parámetros son algo así como constructor(private prop: Type) , donde una propiedad (campo) y un parámetro se declaran juntos. El problema es que ambos se pueden decorar, por lo que es posible que deba inventarse alguna sintaxis imaginativa.

Pregunta: ¿Se pueden usar los decoradores para modificar el comportamiento de un método de objeto simple, como ...

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() {}
};

... y si no, ¿por qué? Será realmente útil.

Todavía no, pero lo estamos considerando. @rbuckton podría tener más detalles.

¿Puedo preguntar por qué el decorador no puede cambiar el tipo de resultado decorado del tipo que se está decorando?

Por ejemplo, MethodDecorator se puede proponer alternativamente de la siguiente manera:

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

Por lo tanto, decorator puede ser más flexible de usar y funciona como una macro. El tipo de clase / campo decorado se puede actualizar de forma correspondiente al tipo de devolución del decorador, de modo que se mantenga la seguridad del tipo.

Sé que https://github.com/jonathandturner/brainstorming/blob/master/README.md#c4 -defining-a-decorator ha declarado que el tipo de retorno debe ser el mismo. Pero type validity no es un argumento válido aquí, en mi humilde opinión. Primero, los decoradores en JavaScript pueden alterar el tipo de clase / campo, por lo que no hay problemas de compatibilidad entre TS y JS. En segundo lugar, los decoradores se aplican estáticamente, el compilador de TS puede razonar sobre el tipo de campo original y el tipo de campo decorado en el sitio definido.

@HerringtonDarkholme Yo diría que esto debe rastrearse en una sugerencia diferente. La razón principal es la forma en que el compilador razona sobre los tipos es a través de declaraciones. una vez que se declara un tipo, no puede cambiar. agregar mutadores de tipo en la mezcla significa que debemos resolverlos ya que estamos resolviendo la declaración que puede complicarse.

@mhegazy entonces, ¿puedo entender esto como un compromiso para la facilidad de implementación, en lugar de un diseño deliberado?

Según esta propuesta , los decoradores restauran la capacidad de ejecutar código en tiempo de diseño, mientras mantienen una sintaxis declarativa. Entonces el siguiente código será
válido en JavaScript del futuro, pero no válido en TypeScript de hoy (sin embargo, esto podría ser aceptado por TS algún día, ¿verdad?).

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)

Sé que las posibilidades de admitir esta función son pequeñas. Pero quiero confirmar que esta no es una historia similar como structural vs nominal escribiendo.

La comprobación actual es que debe devolver algo de su decorador que se pueda asignar al objetivo. la parte que falta es que no estamos capturando ninguna adición al tipo. No creo que tengamos una idea de cómo hacer eso, pero tampoco lo hemos pensado realmente. así que creo que es una sugerencia justa, pero el costo de implementarla puede ser muy alto.

Idealmente, creo que el tipo devuelto por el decorador debería ser idéntico al objetivo. No se sabe si usará el tipo decorado en una posición de origen o de destino en una asignación.

Aunque supongo que realmente deberíamos fusionar el tipo de retorno del decorador y el objetivo, como una mezcla.

Solo una idea de funcionalidad adicional. Dado que la salida del compilador de TypeScript es JavaScript, existen algunos lenguajes similares para Java, como Xtend

Puede conocer algunas ideas interesantes sobre este proyecto. Busque anotaciones activas "Las anotaciones activas permiten a los desarrolladores participar en el proceso de traducción del código fuente Xtend al código Java a través de la biblioteca"

¡Controlar la salida de TypeScript a través de decoradores será una característica realmente agradable!

Los decoradores de interfaces deberían ser posibles.

@shumy El problema es que las interfaces no existen en tiempo de ejecución de ninguna manera, y los decoradores se ejecutan únicamente en tiempo de ejecución.

¿Hay alguna razón para tratar a los decoradores de propiedades de manera diferente a los decoradores de métodos con respecto al valor de retorno? (Babel no lo hace).

Lo que quiero decir es que, si quiero definir la funcionalidad de una propiedad a través del decorador, tengo que hacer esto:

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

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

Mientras que Babel requiere que se devuelva el descriptor:

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

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

Esto hace que sea difícil escribir un decorador en TypeScript que sea parte de una biblioteca que se utilizará desde Babel.

Propondría que un decorador de propiedades sea tratado de manera idéntica al decorador de métodos, y el valor de retorno del decorador debe aplicarse a través de Object.defineProperty . Esto también permitiría múltiples decoradores en una sola propiedad, como un método.

Una solución alternativa por ahora (para compatibilidad con Babel) es devolver el descriptor del decorador además de configurar la propiedad, pero eso parece innecesariamente un desperdicio:

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

@mhegazy , ¿tendría alguna idea sobre mi comentario anterior?

@sccolbert En TypeScript, optamos por no permitir el uso de descriptores de propiedad para decoradores en propiedades de datos, ya que puede generar problemas en tiempo de ejecución debido al hecho de que cualquier "valor" especificado por el descriptor se establecerá en el prototipo y no en la instancia. . Si bien su ejemplo anterior generalmente no sería un problema, considere lo siguiente:

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 (!)

Existe una propuesta para una versión futura de ES para admitir una propiedad de "inicializador" en el descriptor, que se evaluaría durante el constructor. Eso le daría la capacidad de controlar si el valor se establece en el prototipo o se asigna por instancia.

Por ahora, puede solucionar esta limitación llamando a Object.defineProperty directamente, según el ejemplo de su comentario. Continuaremos investigando si relajar esta restricción en el futuro.

@rbuckton gracias! ¿Están en conversaciones con Babel para converger en la misma semántica? Creo que eso es lo más importante.

¿Por qué sería útil utilizar un decorador para especificar un valor para una instancia en particular? ¿No es para eso el inicializador de propiedades?

Mi caso de uso es más complicado que el ejemplo. De hecho, estoy usando el decorador para definir un captador que devuelve un objeto vinculado al contexto this de la propiedad, de modo que los métodos de dicho objeto tengan acceso a la instancia en la que se definió.

Puede pensar en esto como una emulación del protocolo descriptor en Python, donde el método de acceso bar definido en la clase Foo través de la instancia foo (es decir, foo.bar ) invoca la __get__ método de la función que devuelve BoundMethod . Cuando se llama a ese objeto, se invoca la función subyacente con self como primer argumento, que en este caso es foo . Javascript no tiene este concepto, por lo que tenemos que pasar thisArg y llamar a function.bind todas partes.

En mi caso, no estoy definiendo métodos, sino una señal de tipo seguro. Aquí está el decorador:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L144

Cuando se accede a la propiedad, devuelve una implementación de ISignal que está vinculada al contexto this del propietario. Esto permite que la señal vuelva a referirse a la instancia en la que se definió:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L263

Entonces, en efecto, estoy usando el decorador como un atajo para este equivalente detallado:

class Foo {
  valueChanged: ISignal<number>;
}

defineSignal(Foo.prototype, 'valueChanged');

@rbuckton

No se permiten decoradores al apuntar a ES3

¿Por qué? No veo nada que impida el uso de __decorate en ES3.

Utiliza el descriptor de propiedad, que no está disponible en ES3
Le 4 sept. 2015 2:03 p. M., "Koloto" [email protected] a écrit:

@rbuckton https://github.com/rbuckton

No se permiten decoradores al apuntar a ES3

¿Por qué? No veo nada que impida el uso de __decorate en ES3.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/2249#issuecomment -137717517
.

@sccolbert Puede hacerlo mejor con proxies ES6 en lugar de decoradores de propiedades.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Los navegadores

@cybrown Sí, usa el descriptor de propiedad para métodos y descriptores de acceso. Probé solo en propiedades simples (campos sin accesos). Pero parece que se pueden permitir decoradores en ES3 para propiedades (sin accesos) y clases. Seria útil.

Y se puede usar un descriptor de propiedad falso para los métodos cuando se apunta a ES3. Algo como { writable: true, enumerable: true, configurable: true } . Así que no veo ninguna razón para no admitir ES3.

@sccolbert ya veo. Eso tiene sentido. Por cierto, TypeScript está trabajando para mejorar la escritura de _esta_ funciones y métodos. Me pregunto si eso sería de alguna ayuda aquí. Aunque supongo que escribir no es un problema para ti, es la semántica en tiempo de ejecución.

@JsonFreeman La escritura de

Creo que la discusión más desarrollada sobre _este_ mecanografiado está en # 3694.

¡Salud!

error TS1207: Los decoradores no se pueden aplicar a varios descriptores de acceso get / set del mismo nombre.

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

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

el código anterior no está permitido, incluso tiene más sentido en comparación con

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

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

Getter y setter se definen en una llamada a Obect.defineProperty. Esto es más bien un capricho de js, la declaración de set y get aunque separados, en realidad son la misma declaración de propiedad. La verificación de errores en el compilador es para alertar a los usuarios cuando piensan en ellos por separado; los decoradores se aplican solo una vez al descriptor de propiedad.

solo me preguntaba, dado que el compilador puede detectar el get y set con el mismo nombre y combinarlo en un solo Object.defineProperty, ¿por qué no combinar también el decorador?
o quizás un indicador de opción para decirle al compilador que los combine y deje un mensaje de advertencia en lugar de arrojar un error.

gracias

@TakoLittle : La razón por la que no hacemos esto hoy se debe en parte a cómo están compuestos los decoradores. Los decoradores siguen los mismos principios que la composición de funciones matemáticas, donde (_f_ ∘ _g _) (_ x_) se compone como _f _ (_ g _ (_ x_)). En el mismo sentido, se puede pensar que:

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

Es aproximadamente:

F(G(X))

La composicionalidad de los decoradores se rompe cuando decoras tanto el getter como el setter:

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

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

¿Cómo se componen aquí F y G ? ¿Se basa exclusivamente en el orden de los documentos (es decir, F(G(X)) )? ¿Son cada conjunto de decoradores para el captador y el definidor discretos y luego se ejecutan en el orden del documento (es decir, G(F(X)) )? ¿Implican get y set algún pedido específico (es decir, el get siempre antes del set o viceversa)? Hasta que estemos 100% seguros del enfoque más consistente que no sorprenda a los usuarios, o tengamos un enfoque bien documentado que sea parte de la propuesta de los decoradores con al menos la etapa 2 o mejor aceptación dentro de ECMA-262, creemos que es mejor ser más restrictivo y erróneo aquí, ya que nos permite relajar esa restricción en una fecha posterior sin introducir un cambio importante que podría pasar desapercibido fácilmente y posiblemente dar lugar a comportamientos inesperados en tiempo de ejecución.

@rbuckton muchas gracias por la explicación detallada
¡Buen trabajo del equipo de TS! ^^ d

¿Dónde está la documentación para esto? y le importa vincular el compromiso de implementación?

Gracias.

@mhegazy ¿Cuál es el estado de la implementación de la última versión de la especificación? Entiendo que hay algunos cambios allí.

Este problema siguió la versión original de la propuesta. ya que esto está completo, estamos cerrando este problema. Para cualquier actualización de la especificación, registraremos nuevos problemas y describiremos todos los cambios importantes. No creo que la propuesta esté ahora en un lugar en el que esté lista para que la aprovechemos. Estamos trabajando en estrecha colaboración con @wycats en la nueva propuesta.

@mhegazy Gracias por la actualización. Me encantaría estar informado. Cuando cree el nuevo problema para la actualización de especificaciones, vincúlelo aquí para que pueda recibir una notificación y seguirlo. La comunidad de Aurelia hace un uso intensivo de decoradores y queremos sincronizarnos con TypeScript y Babel en la actualización. Nuevamente, gracias por el gran trabajo que está haciendo el equipo de TS.

La decoración funcional es necesaria, por supuesto.
¿También hay planes para decorar otros objetos en el código?

¿Fue útil esta página
0 / 5 - 0 calificaciones