Typescript: Decoradores

Criado em 7 mar. 2015  ·  139Comentários  ·  Fonte: microsoft/TypeScript

Proposta ES7

A proposta ES7 para decoradores pode ser encontrada aqui: https://github.com/wycats/javascript-decorators
A proposta ES7 serve de base para esta proposta. Abaixo estão notas sobre como o sistema de tipos

Alvos do decorador:

Construtor de classe

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

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

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

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

Propriedades

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

desugars para:

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

Parâmetro formal do método / acessador

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

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

Onde o __decorate é definido 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;
};

Assinaturas do decorador:

Um decorador válido deve ser:

  1. Atribuível a um dos tipos de Decorator (ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator) conforme descrito abaixo.
  2. Retorne um valor (no caso de decoradores de classe e decorador de método) que pode ser atribuído ao 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:

  • Decorar uma declaração de função não é permitido, pois isso bloqueará a elevação da função para o topo do escopo, o que é uma mudança significativa na semântica.
  • Expressões de função de decoração e funções de seta não são suportadas. O mesmo efeito pode ser obtido aplicando a função de decorador como var x = dec(function () { });
  • Os parâmetros formais da função de decoração atualmente não fazem parte da proposta ES7.
  • Decoradores não são permitidos quando almejando ES3
Committed ES Next Fixed Suggestion

Comentários muito úteis

A decoração da função é necessária, claro.
Também existem planos para decorar outros objetos no código?

Todos 139 comentários

Desculpe-me pelo que entendi da especificação, não seremos capazes de fazer:

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

Estou certo ?

Como a serialização de tipo funciona com argumentos restantes?

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

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

Usar decoradores parece bastante simples, mas achei as seções sobre como declará-los confusas. C.4 diz que os decoradores precisam ser anotados com @decorator , mas nenhum dos exemplos realmente mostra isso acontecendo.

As fábricas de decoradores devem ser classes que implementam as interfaces encontradas em B?

Qual é a regra para refinar a interpretação de CoverMemberExpressionSquareBracketsAndComputedPropertyName?

Notei que muitas das digitações têm Function | Object em vários pontos, mas eles degenerarão para Objeto no momento da verificação de tipo. Qual é a razão de ter Function lá?

Eu não sou louco pelos termos DecoratorFunction vs DecoratorFactory. Prefiro seguir a nomenclatura dos geradores, que tem Generator e GeneratorFunction. Com esse esquema, renomearíamos DecoratorFunction para Decorator e DecoratorFactory para DecoratorFunction.

Para as exportações decoradas, para que serve [lookahead ≠ @] ? HoistableDeclaration e ClassDeclaration podem realmente começar com @ ?

Esta é uma cópia de # 1557

Não é realmente um idiota, já que o # 1557 era para um design diferente. Esse problema é para o design dos decoradores que está sendo implementado agora.

Meu erro.

Para decorador na expressão de função, não poderíamos fazer algo como:

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

transformado em:

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

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

É um pouco incômodo para alguns ter que corrigir todas as funções como:

const myFunc = function () {}

Você perde o içamento e function.name

Implementação adicionada no PR # 2399

Atualizações: Proposta atualizada e link adicionado para os decoradores JavaScript do @wycats ES7.

triste que se tornou uma coisa só de classe ...
E o decorador ambicioso que eles tiraram do escopo?

@fdecampredon com sua proposta de funções, parece que você ainda perde o içamento.

@JsonFreeman por quê? se você inserir _t no topo do arquivo?

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

Além disso, mesmo que minha proposta tenha muitos problemas, eu gostaria seriamente de poder usar decoradores na função (mesmo se eu tiver que usar a função atribuída à variável) e perder içamento.
Um caso como este parece um caso de uso de decorador muito bom para função e classe (especialmente em conjunto com decoradores de ambiente se eles ainda estiverem no escopo)

@fdecampredon isso não funciona para o caso geral, pois os decoradores são expressões em si. por exemplo

myFunc();  // assumes function declaration is hoisted

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

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

se você içar a declaração de função e a aplicação do decorador, você quebrará o decorador. se você apenas içar a declaração da função, mas não o aplicativo decorador, poderá testemunhar a função em um estado não decorado. sem soluções atraentes aqui.

este é o mesmo problema da cláusula de extensão da declaração de classe, que no ES6 é uma expressão. o resultado não foi içar a declaração da classe, apenas o símbolo (semelhante à declaração var, mas não as declarações de função)

Oups não pensei sobre isso, obrigado @mhegazy.
No entanto, por que a parte funcional abandonou completamente a proposta original @jonathandturner tinha a regra:

decorated function declarations cannot be hoisted to the containing scope

Perder o içamento é certamente uma desvantagem, mas acho prejudicial transformá-lo em um recurso somente de classe quando teria um caso de uso para outro construto.

Vamos ver o que o conjunto de restrições desejado parece implicar:

  • função decorada deve ser içada
  • a função deve ser decorada assim que estiver disponível - portanto, o aplicativo do decorador deve ser içado
  • decorador deve ser definido antes de ser aplicado - portanto, a própria definição de decorador (ou todas as entidades referenciadas pela expressão do decorador) deve ser içada
  • a expressão do decorador é avaliada no local em que é aplicada lexicamente - portanto, o aplicativo não pode ser içado

A única resolução que posso ver para isso é a seguinte: Para decoradores de função, permitimos apenas algo no formato @identifier . Não permitimos uma expressão do lado esquerdo. Além disso, o identificador deve ser uma referência direta a uma declaração de função (incluindo uma função decorada). Todas as decorações de função que ocorrem no escopo devem ser emitidas no topo do escopo, na ordem em que foram aplicadas.

O problema de quebrar as regras de içamento é que isso é surpreendente. Se você estiver escrevendo javascript por um tempo, você espera que as declarações de função estejam disponíveis antes de serem declaradas lexicamente; agora, ao adicionar um marcador sintático aparentemente simples (o decorador), essa natureza fundamental da declaração de função é alterada.

Dito isso, a proposta ES7 ainda está em seus estágios iniciais, então espero que ela evolua e se expanda; portanto, é concebível que a proposta final inclua funções de alguma forma.

Eu acho que eles deveriam ser içados. Estou dizendo que as únicas decorações que permitimos em funções são decorações que, definitivamente, são içadas. A saber, identificadores que fazem referência a declarações de funções.

Mas há outro problema aqui. Na verdade, é impossível içar simultaneamente todas as funções decoradas e garantir que elas não sejam observadas em seu estado não decorado. O problema pode ser visto com um 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
}

Mesmo que ambas as funções sejam içadas, qual é decorada primeiro? Se dec2 for decorado primeiro, então dec1 não será decorado no momento em que for aplicado a dec2.

Portanto, teríamos que escolher entre o seguinte:

  • As funções não são içadas
  • As funções não decoradas são içadas, mas os aplicativos do decorador não são içadas com elas.

Embora eu não goste de nenhum desses, nem mesmo acho que outra coisa seja possível.

Esta é a proposta JS. portanto, o motor não saberia se a expressão se refere a uma declaração de função ou não, mas com a análise estática poderíamos dizer. considere isto:

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

dec2 = undefined;

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

ECA! Javascript não está ficando mais simples. :-)

Seria mais fácil habilitá-lo apenas na função _expressions_:

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

Expressões de função ou lambda não são içadas.

É possível ter parâmetros como este em um decorador no typescript 1.5.0-alpha?

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

Ok, deixa pra lá, apenas crie uma fábrica que leve os parâmetros e retorne a função de decorador real.

Por exemplo, um decorador de classe com um parâmetro de string:

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

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

}

saudações.

Estou tentando descobrir como escrever um decorador que irá selecionar os tipos declarados no construtor.

aqui está um código que ilustra o que estou tentando fazer, mas é fisicamente conectado. eu preciso responder à declaração do construtor na 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;
}

Receio que as informações de tipo estejam fora do escopo do recurso do decorador.
Você pode usar ParameterDecorator em cada parâmetro para adicionar esse tipo de informação.

A digitação ou implementação para ParameterDecorator não está totalmente correta, a partir das digitações, o destino é uma função, mas ao usar o texto, é o objeto de protótipo.
Devo lançar o parâmetro de destino para obter o tipo certo:

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

Ao invés de:

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

uau - regra dos decoradores de parâmetro !!!!!!!!!!!!!!! veja este repo

aqui está o resultado da execução dos testes:

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

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

Eu fiz um wrapper em torno do express (mas qualquer estrutura da web pode ser suportada, uma interface de adaptador é definida) com uma API baseada em decorador: https://github.com/cybrown/web-decorators

Estou usando ClassDecorator, ParameterDecorator e MethodDecorator.

@cybrown Acabei de atualizar a assinatura de ParameterDecorator em # 2635, que agora está no master.

@rbuckton Obrigado, vou atualizar isso amanhã no meu projeto.

É possível ter o nome do parâmetro em um ParameterDecorator?
Isso pode ser útil porque acho que o nome do parâmetro pode ser metade do tempo o primeiro argumento de um ParameterDecorator.
Além disso, pode ser uma solução para mutilar nomes de parâmetros por minificadores.

me pediram para fazer isso:

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

mas sem especificar as chaves no decorador injetar, mas, em vez disso, fazer com que os decoradores selecionem as funções de classe do construtor automaticamente com algo ao longo destas linhas:

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

estou procurando como fazer o que @jonathandturner descreve aqui

@cmichaelgraham Sim, ter informações de tipo de tempo de execução (rtti conforme descrito no que era AtScript) seria incrível para esse tipo de uso, mesmo em um recurso completamente separado, como introspecção.

@cmichaelgraham Estamos trabalhando em uma proposta para o ES7 adicionar uma API de metadados que funcionará junto com decoradores. # 2589 adiciona suporte experimental para essa proposta, mas requer um polyfill separado presente para ler os metadados.

@rbuckton @cmichaelgraham Havia originalmente um design para um decorador TypeScript especial que dizia ao compilador para injetar tipos no decorador, com base no destino sendo decorado. Acho que foi @parameterTypes Estou correto em pensar que esse recurso está sendo removido em favor da API de metadados mais geral? Se sim, eu apoiaria isso.

Você pode falar um pouco sobre o status disso em relação ao TypeScript. Isso está planejado para o lançamento do 1.5? Em caso afirmativo, como serão as opções do compilador? Uma coisa que seria útil é fazer com que o compilador gere automaticamente os metadados de tipo apenas para assinaturas de construtor.

Os decoradores 1.5-alpha .

Quanto ao suporte de metadados. O desenho mudou desde a proposta original para @paramtypes . o novo desing está usando a proposta Reflect.metada. Veja # 2589 para mais detalhes sobre como funciona. também @rbuckton tem um pollyfill para consumir os metadados aqui: https://github.com/rbuckton/reflectDecorators

@mhegazy eu sei disso. Veja aqui: http://blog.durandal.io/2015/04/09/aurelia-update-with-decorators-ie9-and-more/ : smile:

Também estou familiarizado com a proposta de metadados. Tenho fornecido feedback sobre isso. Só queria saber se a ideia original @parameterTypes estava sendo removida.

obrigado @EisenbergEffect por compartilhar. : +1:

sim. o único problema com @paramtypes é que ele faz a emissão direcionada pelo sistema de tipos, e requer informações globais do programa. Isso interrompe os cenários de transpilação de módulo único (consulte # 2499). A outra opção era colocá-lo em sites de chamadas, o que adiciona muito trabalho aos usuários decoradores em vez de aos autores. Então, voltamos para a prancheta e pousamos na abordagem Reflect.metadata.

se você também olhou para as versões anteriores da proposta, tivemos que remover os decoradores de ambiente / design-time pelo mesmo motivo.

Obrigado por esclarecer. Tudo isso faz sentido. Alguma ideia se a abordagem baseada no Reflect chegará ao 1.5?

sim. está atualmente em master. ele deve estar disponível na próxima versão. atualmente é um recurso opcional que usa a sinalização experimental --emitDecoratorMetadata ; e só adiciona metadados a entidades decoradas.

"ele apenas adiciona metadados a entidades decoradas" Você pode expandir essa ideia? Um decorador especial está envolvido? Ou adiciona a qualquer decorador? Em outras palavras, se um desenvolvedor usar o decorador inject Aurelia, isso fará com que o compilador gere os metadados?

se um desenvolvedor usar o decorador de injeção do Aurelia, isso acionará o compilador para gerar os metadados?

sim.

o que eu quis dizer é:

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

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

com --emitDecoratorMetadata o compilador emitirá metadados de tipo (ou seja, chamar reflect.metadata('desing:paramtypes', [Number, String]) ) para Foo mas _not_ Bar .

Isso vai funcionar para nós. Vamos preparar o suporte para a API Reflect.metadata em nosso próximo lançamento, provavelmente na próxima semana. Obrigado pelo esclarecimento!

então o que é emitido para isso?

`` `linguagem = javascript
@injetar
class Foo {
construtor (a: A, b: B) {}
}

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

would it be (for Foo):

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

@cmichaelgraham Sim, é mais ou menos isso que é gerado.

Muito legal !!!!

usando gulp-typescript para construir este repositório - (bom trabalho @ivogabe)

comando gulpfile build-ts (observe a opção 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('.'))
    ]);
});

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

em 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 interesse especial são os metadados de tipo emitidos sobre os parâmetros do construtor:

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

então, em teoria, você poderia alterar a função injetora de decorador para injetar as classes adequadas sem especificá-las no injetar, mas, em vez disso, especificar os tipos no construtor :)

@cmichaelgraham Podemos habilitar isso facilmente hoje, sem alterar a estrutura. A biblioteca DI de Aurelia tem um gancho container.addParameterInfoLocator . Você passa uma função que pode receber um tipo e retornar suas dependências. Para nosso próximo lançamento (na próxima semana), podemos adicionar isso à configuração principal, portanto, é fácil para os desenvolvedores do TypeScript ativá-lo. Eu teria colocado no lançamento desta semana, mas não tinha percebido que isso havia mudado ainda.

@EisenbergEffect brilhante !! : +1:

Usei anotações para marcar as propriedades de um objeto como observáveis, o que transforma um primitivo em um objeto observável (para os interessados, https://github.com/mweststrate/MOBservable/commit/8cc7fc0e20c000db660037c8b5c9d944fe4155d9).

No entanto, especialmente para propriedades, parecia um pouco anormal que as anotações fossem aplicadas ao protótipo, e não ao construtor de uma classe em si, isso significa que é difícil obter o valor inicial ou mesmo o this . A solução para isso foi criar uma propriedade e em seu getter / setter executar a lógica real que a anotação deveria fazer, uma vez que this e o valor estão então disponíveis.

Além disso, recurso incrível!

Usando a API Metadata Reflection, consegui fazer uma injeção de dependência de propriedades simples.
Você pode verificar as alterações necessárias aqui https://github.com/matjaz/property-DI/commit/2b4835e100b72d954b57d0e656ea524539ac17eb.

Não deve ser __metadata decorator no código gerado invocado primeiro de todos os decorators? Eu queria criar um deocrator simples usando os metadados da classe, mas __metadata ('design: paramtypes', [TYPES ....]) é invocado por último, então não posso obter esses dados do Reflect

@ufon, você pode compartilhar o código que está vendo?

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

EDITAR:
meu mal, há algum outro erro no meu código, não consigo obter tipos mesmo após a inicialização da classe

@ufon , não tenho certeza se vejo o problema então. __metadata é o último item na lista de decoradores, isso significa que é o primeiro a ser executado e, definitivamente, antes que o injete seja executado.

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

onde __decorate definition usa o direito de redução para executar os decoradores na ordem reversa.

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

Isso é para coincidir com a especificação, que decoradores (como expressões) são avaliados em ordem de declaração, mas executados na ordem inversa, permitindo que decoradores externos consumam resultados de decoradores internos.

Entendo, desculpe o aborrecimento :) meu mal, há algum outro erro no meu código, não consigo obter tipos mesmo após a inicialização da aula

como você está perguntando por eles? e você tem um pollyfill para Reflect.metadata?

adicionado à essência ...
Reflect.getMetadataKeys (House);
isso resulta em uma matriz vazia ..

Sim, estou exigindo o pacote 'refletir-metadados'

parece que o polyfill está perdendo-o, @rbuckton pode dar uma olhada.

Acho que entendi ...
requer são movidos após o código auxiliar do typescript

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

então, com o tempo var __metadata está sendo inicializado, polyfill ainda não foi carregado

mas não consigo obter estes requer antes do código gerado

EDIT: atualizou a essência .. quando um único módulo é compilado, não há como carregar o polyfill antes que __metadata seja resolvido, porque o código gerado do typescript é sempre movido para o topo do arquivo

ooh .. este é um problema maior. problema registrado nº 2811 para rastreá-lo

Até então, você precisa de reflect-metadata em outro módulo (você precisa de dois arquivos).
consulte https://github.com/matjaz/property-DI/

Parece haver uma inconsistência de invocação que torna muito difícil desenvolver decoradores com parâmetros variáveis ​​/ opcionais. Como um exemplo:

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

@F()
prop: number;

No primeiro, F é chamado com os parâmetros (target, propertyName, propertyDescriptor) enquanto no último, F é chamado com os parâmetros () e requer que você tenha uma função interna retornada que por sua vez é chamada com (target, propertyName e propertyDescriptor).

Minha _assunção_ seria que @F e @F () seriam equivalentes e, portanto, você só pode executar qualquer uma das chamadas se o decorador oferecer suporte a 0 ou mais parâmetros.

Isso é particularmente importante no caso:

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

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

Isso pode ser contornado apenas chamando explicitamente @F () em vez de @F, mas é fácil cometer esse erro e então ficar confuso quando seu decorador não funcionar, embora pela aparência devesse.

Neste caso, gostaria de fazer o seguinte:

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

e pronto, mas, em vez disso, tenho que fazer algo como o seguinte exemplo bruto:

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

O que é uma verdadeira dor.

@Tharaxis esse comportamento é @F e @F() não são equivalentes. @F chama um decorador F , onde como @F() chama um fator de decorador F com lista de argumentos vazia e, em seguida, aplica o decorador resultante.

O problema aqui é que o compilador compara a assinatura da fábrica de decoradores F com declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; que acaba sendo atribuível. Acho que precisamos de uma verificação mais forte aqui para garantir que detectamos esses problemas. Registrei o número 3246 para corrigir isso. Eu acredito que você está fazendo a coisa certa, e o compilador deve detectar usos inválidos de uma fábrica de decoradores.

@mhegazy Não tenho certeza de por que esse é o caso, no entanto, há algum caso de uso que impede alguém de tornar @F e @F() equivalentes, uma vez que, em última análise, ambos são uma invocação idêntica da função decoradora , apenas um deles inclui também uma invocação para uma função de fábrica externa? Parece que o princípio da menor surpresa está sendo violado aqui, pois esse é um comportamento definitivamente bastante surpreendente e não vejo por que deve haver 2 maneiras de invocar um decorador da perspectiva do consumidor da API.

Embora eu entenda isso da perspectiva de JS, há uma diferença (em um você está passando a referência da função F e no outro você está executando uma função e, em seguida, passando a referência de seu resultado como o decorador), não estou tenho certeza de que entendo por que não se pode simplesmente ter decoradores que são passados ​​como @F implicitamente combinando (e invocando) a fábrica com uma lista de parâmetros vazia?

O compilador não tem como saber se é uma fábrica ou um decorador. se você tiver esta assinatura:

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

É um decorador ou uma fábrica, deve ser chamado com argumentos vazios ou não?

O máximo que o compilador pode fazer é verificar se a assinatura corresponde ao chamá-la de uma forma ou de outra e fornecer erros se não corresponder.

@rbuckton tem uma correção para # 3246 que deve sinalizar os casos em que seu decorador não é usado como uma fábrica, portanto, fique atento.

@mhegazy , o que estou dizendo é que o uso de um decorador (ou seja, @F ou @F() ) deve _sempre_ ser uma invocação da função de fábrica F, que por sua vez retorna o tipo de decorador apropriado. Não há representação de nível superior de um decorador sem uma fábrica de encapsulamento.

Em outras palavras, a assinatura de F é sempre:

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

Ou algum equivalente.

Então, a invocação @F é equivalente a @F() pelo compilador. Como a assinatura de @F e @F() devem corresponder à assinatura de fábrica de alguma forma, isso resolve o problema de confusão de invocação. Só existe uma maneira de invocar um decorador!

Dado o seguinte:

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

@F()
property: number;

e

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

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

Eles fazem a mesma coisa, mas eu tenho que declará-los de forma diferente! No entanto, declarar F para que ele possa existir de ambas as maneiras é realmente complicado e impossibilita a criação de parâmetros e documentação consistentes. Você está escolhendo se seu decorador precisa usar uma fábrica ou não, e seu consumidor de API precisa saber essa distinção de antemão para fazer uso dela.

@Tharaxis Se estou olhando para uma biblioteca que fornece F que será usada como decorador, eu absolutamente espero que @F e @F() sejam coisas diferentes. Assim como eu esperaria que C e C() fossem coisas diferentes - o primeiro faz referência a um valor, o segundo invoca um valor. @F aplica um decorador, @F() invoca uma função que criará um decorador. Essas não são, e não deveriam ser, a mesma operação.

@DavidSouther existe alguma razão válida pela qual isso não pode ou não deve ser o caso? Não há diferença funcional entre uma invocação de @F e @F() mas há uma grande diferença entre como eles são definidos e documentados e adiciona complexidade adicional à invocação que deveria ser desnecessária.

Minha proposta é que não deve haver duas maneiras de definir um decorador porque não há necessidade, e não deve haver necessidade de definir um decorador mais de uma maneira.

Não há diferença funcional entre uma invocação de @F e @F ()

Acho que esse é o principal ponto de discórdia. Eu não acho que isso seja verdade. F é uma referência a uma função, F () é uma referência ao valor de retorno de F quando chamado com um conjunto de argumentos vazio (ou seja, F.call(this, []) ); e essas são duas coisas funcional e conceitualmente diferentes.

@Tharaxis @F e @F() são coisas diferentes. Há uma diferença em como eles são usados, uma diferença em como são documentados e diferenças em sua invocação que são absolutamente necessárias.

Deixe-me perguntar de uma maneira diferente: por que toda função de decorador deve ser uma fábrica? Usando sua proposta, seria impossível ter um decorador simples.

@mhegazy, embora eu admita que a última opção ( @F() ) resulta na geração de um encerramento funcional e sobrecarga adicional por meio da criação de funções decoradoras isoladas (e duplicadas) e a invocação @F essencialmente executa contra uma referência de função de decorador compartilhada, eu diria que o isolamento por meio do encerramento seria "mais seguro" e mais consistente, reduzindo a probabilidade de surpresas.

@DavidSouther Sua pergunta depende do que você define como um decorador simples. Nada impede que alguém tenha um decorador simples na forma de:

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

Acho isso minimamente invasivo, pois tem apenas 2 linhas a mais do que a "sintaxe simples" e define de forma mais óbvia quais são as invocações do decorador disponíveis. Além disso, como há apenas uma única maneira de definir um decorador (e não vamos descartar o valor da consistência), você provavelmente levará mais 2 segundos para descobrir isso em comparação com a "sintaxe simples".

Além disso, uma questão mais problemática é a seguinte:

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;

Uma dessas coisas não é igual as outras. O uso de @F não funcionará, embora pelo valor de face definitivamente devesse. Tenha em mente, finja que eu sou alguém que não está ciente da aparência da declaração subjacente de F, mas, em vez disso, apenas saiba que F existe e que pode receber um conjunto opcional de argumentos. As chances de eu cometer um erro com @F não são triviais no mecanismo atual. Isso coloca um grande ônus sobre o desenvolvedor / documentador para ter certeza de que o consumidor está ciente de que @F não funcionará (mas talvez @C funcionará porque é "diferente" de alguma forma).

Se eu quiser um decorador "tamanho único", onde o consumidor não tenha que se preocupar, tenho que fazer esta coisa horrível:

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

O que é francamente horrível. Além disso, eu perco todos os parâmetros valiosos da intellisense contra o decorador F, pois agora ele precisa ser generalizado.

Há muito a dizer sobre ter decoradores fáceis e consistentes de definir e documentar. Não vamos nos enganar pensando que todos que consomem nosso código terão o mesmo conhecimento ou compreensão que nós, os criadores desse código.

@Tharaxis Pensei nisso no início do design para decoradores e entendo seus pontos específicos. Como @mhegazy e @DavidSouther mencionaram anteriormente neste tópico, esse comportamento é consistente com o comportamento padrão das funções em JavaScript.

Você está correto ao dizer que qualquer tentativa de criar uma função que possa atuar tanto como decorador quanto como fábrica de decoradores pode ser um tanto complexa. Em geral, porém, pode ser melhor empregar uma prática recomendada sempre usar fábricas de decoradores e regras de fornecimento para um linter para garantir que essa prática seja adotada.

Além disso, acabei de enviar PR # 3249 para abordar seu ponto anterior sobre inconsistências na verificação de tipo para decoradores.

Olá @rbuckton ,

Sou um daqueles usuários não tão experientes de decoradores mencionados por @Tharaxis ...

Acho que a pergunta que eu faria é: os decoradores devem ser consistentes com as funções? Ou seja, no caso de funções, retornar this.f versus this.f () faz todo o sentido para mim, pois às vezes eu quero o valor, às vezes eu quero o que produz o valor.

No caso dos decoradores, não parece tão claro para mim no nível de recursos de linguagem. Eu gostaria de argumentar que desejo apenas aplicar o decorator @F , não quero realmente saber se ele foi implementado como um método de fábrica ou estático. A menos que eu perdesse alguma capacidade que os decoradores teriam.

Minhas desculpas se o acima for mal informado ou ignorante, sou relativamente novo no mundo javascript.

Obrigado e saudações

Eu também tenho que fazer essa pergunta - faz sentido manter a paridade sintática com funções _if_ (e somente se) não houver nenhuma razão real para fazer isso, exceto "porque é definido usando funções". Eu vejo Decorators como um recurso totalmente não relacionado a funções que simplesmente _podem_ ser definidas usando-os.

No momento, o fato de @F e @F() não serem idênticos causa os seguintes problemas:

  • Os desenvolvedores precisam entender a diferença entre @F e @F() e por que às vezes isso pode funcionar e por que às vezes pode não funcionar (e a necessidade de usar @F vs. @F() poderia ser inteiramente específico de estrutura / código / uso - portanto, inconsistente do ponto de vista do consumo: Imagine declare function A(params?: any): ParameterDecorator vs declare function B(target, name) , @A não funcionará, mas @A() vai, @B vai funcionar, mas @B() não vai, embora cognitivamente eles sejam a mesma coisa, uma aplicação de um decorador sem argumentos).
  • Resolver o problema a partir de um nível de código requer uma solução alternativa que remove a capacidade de documentar adequadamente a sintaxe de invocação do Decorator.
  • Refatorar é um pouco mais trabalhoso se você criou um 'decorador bruto' ( @F ) e agora deseja adicionar alguns parâmetros (mas torná-los opcionais para compatibilidade com versões anteriores). No método atual, todos aqueles @F se aplicam agora precisam ser refatorados para @F() - alguns dentro do código ao qual você pode não ter acesso - enquanto a invocação implícita permitiria que você mantivesse as coisas funcionando e restringisse o mude para o seu código.

@Tharaxis Eu entendo que a distinção entre @F e @F() pode ser um grande fardo cognitivo para os consumidores. No entanto, parece que você está pedindo que o código emitido e, portanto, o comportamento, mude, para que os dois se comportem de maneira equivalente. Isso provavelmente entraria em conflito com a proposta ES7 para decoradores, uma vez que é muito improvável que as duas formas sejam tratadas da mesma forma. Não será bom se isso levar a uma divergência.

Outro ponto é que a confusão aqui é semelhante à confusão entre passar uma função como um retorno de chamada e chamar a função. Se você tivesse isso

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

Um usuário pode ficar confuso e chamar takesCallback(callbackFactory) sem invocar callbackFactory . É exatamente igual à confusão que você apontou com decoradores, mas se a linguagem normalizasse essas duas formas para fazer a mesma coisa, haveria uma perda de expressividade. Às vezes, a distinção é importante e precisa ser mantida. Não tenho certeza se isso se aplica comumente a decoradores também, mas em teoria certamente poderia.

@JsonFreeman Eu realmente não entendo seu argumento. Você está dizendo que não pode fazer decoradores fazerem sentido como sua própria entidade porque existe uma proposta ES7 para decoradores, mas é isso, decoradores ES7 são apenas isso, uma _proposta_, e pelo que eu sei, eles ' Está parcialmente baseado em uma combinação do trabalho do decorador descrito pelo Google e por você mesmo, correto? Nada foi decidido ainda sobre como eles devem funcionar, e é provável que a proposta passe por _muitas_ iterações antes da aprovação, então por que não fazer com que trabalhem de maneira consistente em TS e então (pelo menos tente) obter as várias partes envolvidas na proposta por trás da ideia, usando a experiência adquirida em TS como base? Esta não seria a primeira vez que o TS implementou um recurso apenas para ter que refatorar mais tarde, porque a especificação ES6 exigia um comportamento ligeiramente diferente. Você _sempre_ terá esse problema, contanto que esteja acompanhando uma especificação que ainda não existe e ainda não foi acordada.

Quanto ao seu exemplo com relação às funções, parece mais uma vez que estamos combinando funções com decoradores quando elas são na realidade (e deveriam ser) construções completamente diferentes. Quando estou trabalhando com uma função em JavaScript, entendo como as funções são usadas e entendo a diferença entre uma referência de função e uma chamada de função (e quando posso querer usar uma em relação à outra) - e quando descrevo algo como função, não há confusão quanto aos usos que ela tem. Decoradores _não_ são funções e não devem herdar o comportamento das funções. As funções são apenas o método sobre o qual os decoradores são construídos. É como dizer que classes são funções, o que não é o caso, funções são apenas a maneira pela qual descrevemos as classes antes do ES6, porque não havia nenhum método mais óbvio disponível. As classes não assumem as propriedades das funções, você não pode chamar classes como funções, mas você as declara usando funções anteriores ao ES6.

Apesar de tudo, sinto que esgotei meus argumentos sobre o assunto. Vou ter que trabalhar em torno da escolha. Em meu próprio código, sempre usarei fábricas para manter minha consistência. Ainda acredito que tratar os decoradores literalmente como funções provavelmente causará muita frustração no futuro, mas isso é só comigo.

@mhegazy Este é o mesmo problema que mencionei no @jonathandturner aqui: https://github.com/jonathandturner/decorators/issues/16 Acho que isso é algo que deve ser considerado. Da forma como está, espero que essa escolha de design seja o assunto de muitos posts com o título de algo semelhante também "ES7 Decorator Pitfalls".

@Tharaxis , concordo com seu primeiro ponto. Não acho que uma proposta ES7 deva nos impedir de projetar algo bem, e nosso design é de fato um dos precursores do ES7 de qualquer maneira.

Quanto ao "argumento dos decoradores não são funções", se não me engano, não estamos a falar dos próprios decoradores, estamos a falar de fábricas de decoradores. E independentemente de qualquer distinção entre decoradores e funções, acho que decoradores _factories_ são, na verdade, apenas funções. E parece que você está pedindo que o compilador gere automaticamente uma chamada para uma fábrica de decoradores, que é uma função comum. Se isso acontecesse, não haveria como o programador distinguir entre a fábrica do decorador e o decorador.

Além disso, o bom de tratar decoradores como funções é que um consumidor pode aplicá-lo como decorador ou simplesmente chamá-lo como achar melhor.

@JsonFreeman o argumento gira em torno da situação atual em que há duas maneiras de descrever e invocar um decorador, uma por meio de uma função de fábrica e outra como uma função "bruta" (que no caso de fábrica é o resultado de uma chamada de fábrica ), e a questão era mais sobre "não deveria haver apenas uma maneira e as fábricas seriam assim".

O que estou perguntando é sim, não poderíamos simplesmente fazer com que o compilador transformasse @F no equivalente a @F() chamada e fazer com que a verificação de tipo exigisse uma lista de argumentos de 0 ... n parâmetros ao usar o sintaxe sem colchetes. Talvez você possa elaborar sobre o que significa "... não haveria como o programador distinguir entre a fábrica de decoradores e o decorador ...", já que eu acho que é muito fácil distinguir. O decorador é sempre a resposta da fábrica, e o nome da fábrica é o nome do decorador ... não é tão difícil, ou estou entendendo mal?

Quanto ao seu último ponto sobre permitir que o consumidor apenas aplique o decorador, se estiver bem descrito que todos os decoradores usam fábricas, é muito fácil invocar um decorador você mesmo, basta fazer <decorator name>(<argument list>)(target, name) comparação com <decorator name>(target, name) como exemplo. Tenha em mente que obrigar o uso de fábricas significaria que o primeiro exemplo sempre funcionará, enquanto não obrigar resultará no último exemplo às vezes não funcionando, e é totalmente dependente de como o decorador é implementado - uma dor de cabeça esperando para acontecer.

Acho necessário apontar, não tenho problemas com decoradores usando funções, meu problema é que ter duas maneiras de descrever a mesma coisa leva a problemas de consistência. Especialmente quando essas duas maneiras também significam que seu método de invocação deve ser diferente. Essa é uma disparidade que impede tudo, desde a refatoração até a consistência da linguagem.

O problema de refatoração que descrevi alguns posts atrás já deve ser uma razão mais do que suficiente para que o método atual precise ser inspecionado.

Olá a todos, apenas 1,5 centavos de dólar final de um usuário regular nos moldes do que @Tharaxis disse.

Eu ficaria mais do que feliz com a proposta atual se:
a) o universo o ordena.
b) os decoradores perderão recursos importantes se as coisas forem diferentes.

O último é, obviamente, um julgamento de valor, para o qual a resposta dependerá até certo ponto do tipo de desenvolvedor que você é (ou seja, usuário geral / usuário especialista, etc.). Estou na primeira categoria e geralmente estou espalhado em várias linguagens e estruturas. Então, para mim, 'recursos importantes' não incluirão flexibilidade para escrever decoradores de duas maneiras diferentes. Exemplos de todas as compensações seriam ótimos se eles existissem.

Idealmente, seria ótimo se @F e @F () pudessem ser consistentes, independentemente de como os decoradores são implementados, mas se não, eu prefiro ser restringido a usar fábricas ao escrever decoradores, em vez de evitar riscos na grama sempre que estou usando.

Obrigado e saudações

Parece que esse pedido depende da ideia de que as fábricas de decoradores são a maneira canônica de usar decoradores, mas não vejo como isso permite ao usuário definir e usar decoradores brutos. Se eu definir um decorador F e o aplicativo @F for tratado como @F() , o resultado de chamar F será usado como decorador, em vez do próprio F. Você está sugerindo que daremos um erro em algum lugar se alguém tentar aplicar um decorador bruto em vez de uma fábrica de decoradores?

Essa ideia parece reverter a composicionalidade natural dos decoradores e das fábricas de decoradores. Os decoradores definitivamente parecem a construção primitiva aqui, com as fábricas de decoradores sendo uma camada de abstração construída em cima dos decoradores. Eles são apenas um padrão, nada mais. Se, em vez disso, a fábrica de decoradores se tornasse a coisa canônica, a primitiva, então as pessoas definiriam um monte de fábricas de decoradores, que não aceitam argumentos e devolveriam um decorador plano. Isso começaria a parecer muito bobo e inverteria a intuição natural do que é considerado básico e do que é considerado mais complexo.

Uma coisa que desconfio dos decoradores é o excesso de magia. Eu pessoalmente fico nervoso quando não entendo o que o código está fazendo, e se o compilador está secretamente adicionando invocações extras que o programador não escreveu, isso me parece muito vodu.

Olá @JsonFreeman ,

Como mencionei, minha preferência seria sempre que a feiura estivesse com o autor do decorador ao invés do usuário. No entanto, concordo que muitas fábricas sem arg é muito feio. Um decorador pode ser usado para consertar isso? Por exemplo

// 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 qualquer forma, se tal abordagem puder ser considerada útil, ela terá a vantagem de que a anotação documenta o propósito da função. ou seja, ele decora.

Saúde

Bom molho, decoradores para ajudar a definir decoradores, um caso sério de Typeception.

@JsonFreeman Não tenho certeza se funções com zero-param são necessariamente mais feias do que ter uma infinidade de funções (destino, nome) em seu lugar. No mínimo, as funções de zero-param fornecem um nível de especificidade / clareza que falta ao outro método (uma vez que os parâmetros nunca correspondem à invocação no último caso) e, além disso, fornece um único alvo para documentar. de um nível desnecessário de inconsistência. Também vejo uma relutância em seguir qualquer caminho devido a uma complexidade percebida "possível", mas eu diria que à luz da complexidade "óbvia" muito real que a implementação atual impõe do lado do consumo, deve-se errar mais do lado do óbvio do que do possível.

A outra opção é determinar que os decoradores chamados de @F sejam capazes de corresponder ao padrão de ClassDecorator, MethodDecorator, PropertyDecorator ou ParameterDecorator, OU uma função de fábrica 0..n arg que retorna ClassDecorator, MethodDecorator, PropertyDecorator ou ParameterDecorator. No entanto, acho que essa implementação causaria outros erros (e se você tiver duas funções conflitantes, qual seria considerada a melhor correspondência possível?) E apenas adicionaria complexidade indevida ao compilador. A simples conversão de @F chamadas em @F() seria a solução mais simples e resolveria os vários problemas apresentados.

Para esclarecer, não estava afirmando que sua solução é complexa. Eu quis dizer que invocar automaticamente o decorador antes de aplicá-lo é opaco. A invocação inserida é invisível para o usuário e sinto que muitos usuários não a esperam.

Acho que a forma como está atualmente também não é complexa. Como você observou, isso permite que os autores de bibliotecas sejam vagos ou indiferentes sobre se sua função deve ser aplicada como decoradores. Isso eu vou conceder a você. Mas uma boa biblioteca deixará isso claro.

No esquema que você sugere, onde você faz a invocação automaticamente, o que acontece quando você usa uma expressão arbitrária do decorador em vez de apenas um identificador? Isso também é invocado?

Concordo que, a menos que seja documentado de outra forma, a invocação implícita provavelmente será surpreendente, mas apenas para aqueles que não usaram conceitos como atributos C # e similares.

Você poderia explicar o que entende por expressão arbitrária do decorador?

Bom molho, decoradores para ajudar a definir decoradores, um caso sério de Typeception.

Chamada justa. Serve para mim para um comentário mal pensado no final de uma tarde de fim de semana. Lição aprendida. Internet.undo ().

Meu ponto é que, se descobrir que a sintaxe consistente do site de chamadas exige o uso de fábricas, então estou mais do que feliz com isso. Sem dúvida, vou escrever para um decorador para remover a placa de caldeira. Mais uma vez, eu prefiro um pouco de dor ao escrever para o decorador do que a dor repetida em usá-los (embora seja uma dor potencial neste momento). Outros discordarão.

Não há também o problema com o aprimoramento da API? Um decorador criado sem uma fábrica não pode ter parâmetros opcionais adicionados posteriormente, portanto, prevejo @F e @F2() no meu futuro.

Também não vejo que você obteria esse cenário ao usar f e f() , eles são casos de uso diferentes no lado do consumo. No caso do decorador, estou sempre aplicando / invocando o decorador no destino ali mesmo, e sempre há uma invocação de método acontecendo nos bastidores.

Mas o ponto crucial disso para mim é a questão de usabilidade, quando estou aplicando um decorador, não quero ter que pesquisar no Google para descobrir como o autor o implementou - estou interessado no comportamento, não na embalagem.

Saúde

Só uma nota final para resumir e depois ficarei quieto. Resumindo, isso é apenas uma questão de design de interação para mim. Estou muito ansioso para ver decoradores projetados de fora para dentro e não o contrário.

Como um cara de UI / UX, vejo isso com bastante frequência. Trabalhei com desenvolvedores talentosos que sugeriram soluções de IU que prejudicariam os usuários (um exemplo foram dois botões de salvar em um caso para resolver a complexidade de uma transação - novamente um cara inteligente e bom humano). Só acho que quando você está mergulhado na lógica de detalhes de implementação complexos, pode ser difícil esquecer o que você sabe e ver através dos olhos do usuário médio.

Agora, se como a média estou simplesmente errado, ou se a complexidade exige o design atual, tudo bem, terei apenas que colocar meu cérebro em funcionamento e aprender.

Saúde

Se o seu decorador não for um identificador, mas alguma outra expressão, você o invocaria automaticamente? Por exemplo, digamos que seja um decorador. OR-ed com uma expressão de função de fallback. Algo como:

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

Você invoca isso automaticamente? Isso daria o resultado errado, porque você acaba invocando o decorador padrão antes de ele ser aplicado:

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

Isso não parece certo.

@JsonFreeman , a sintaxe do decorador permite expressões arbitrárias como essa? Não tenho 100% de certeza de que permitir aos desenvolvedores tanta corda para se enforcar é necessariamente uma boa ideia, mas isso é só comigo (simplesmente porque não vejo expressões arbitrárias como aquela resolvendo o problema de reutilização / clichê que os decoradores pretendem resolver e, em vez disso, apenas servem para tornar o código feio e ainda mais difícil de seguir).

Dito isso, suponho que a única maneira de funcionar seria se a própria expressão arbitrária fosse avaliada para a mesma sintaxe de fábrica, então:

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

Eu concordo que isso resulta em você ter que colocar mais algumas partes complicadas em torno de seu decorador arbitrário, mas acho que há mais problemas do que adicionar () => se você estiver usando expressões arbitrárias como decoradores.

NOTA: Obviamente, não quis dizer que a sintaxe lambda seria a única maneira de declará-la, mas () => é ligeiramente melhor do que function() { return function(target) { /*...*/ } } .

Sinto muito interromper este debate sobre a sintaxe de chamada do decorador, mas alguém poderia esclarecer oficialmente a ordem em que as expressões do decorador são chamadas? Especialmente, ao longo das linhas do tipo de destino do decorador, a posição do membro de destino na fonte original e a ordem dos decoradores em um único destino.

@billccn Os decoradores são aplicados de baixo para cima. Portanto, no código-fonte original, se você tivesse

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

Isso aplicaria primeiro G ao método e, em seguida, aplicaria F ao resultado. É isso que você está perguntando?

Que tal para isso:

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

eles seguem a ordem do escopo, portanto, todas as propriedades / métodos primeiro, na ordem de declaração, depois o da classe. ou seja, B, C, D, E, F, A.

você pode ver o código gerado aqui .

Parece que atualmente não é possível decorar as propriedades dos parâmetros. Eles podem ser apoiados?

Propriedades de parâmetro são algo como constructor(private prop: Type) , onde uma propriedade (campo) e um parâmetro são declarados juntos. O problema é que ambos podem ser decorados, então alguma sintaxe imaginativa pode ter que ser inventada.

Pergunta: Os decoradores podem ser usados ​​para modificar o comportamento de um método de objeto simples, 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() {}
};

... e se não, por quê? Será muito útil.

Ainda não, mas estamos considerando isso. @rbuckton pode ter mais detalhes.

Posso perguntar por que o decorador não pode alterar o tipo de resultado decorado do tipo que está sendo decorado?

Por exemplo, MethodDecorator pode ser alternativamente proposto conforme abaixo:

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

Portanto, o decorator pode ser mais flexível de usar e funciona como uma macro. O tipo de classe / campo decorado pode ser atualizado correspondentemente ao tipo de retorno do decorador, de forma que a segurança do tipo ainda seja mantida.

Eu sei que https://github.com/jonathandturner/brainstorming/blob/master/README.md#c4 -defining-a-decorator declarou que o tipo de retorno deve ser o mesmo. Mas type validity não é um argumento válido aqui, IMHO. Primeiro, os decoradores em JavaScript podem alterar o tipo de classe / campo, portanto, não há problema de compatibilidade entre TS e JS. Em segundo lugar, os decoradores são aplicados estaticamente, o compilador TS pode raciocinar sobre o tipo de campo original e o tipo de campo decorado em definir site.

@HerringtonDarkholme Eu diria que isso precisa ser rastreado em uma sugestão diferente. O principal motivo é a maneira como o compilador raciocina sobre os tipos é por meio de declarações. uma vez que um tipo é declarado, ele não pode mudar. adicionar mutadores de tipo na mistura significa que precisamos resolvê-los, pois estamos resolvendo a declaração, que pode ficar complicada.

@mhegazy , então, posso entender isso como um meio-termo para facilitar a implementação, em vez de um design deliberado?

De acordo com essa proposta , os decoradores restauram a capacidade de executar o código em tempo de design, mantendo uma sintaxe declarativa. Portanto, o código a seguir deve ser
válido no JavaScript do futuro, mas não válido no TypeScript de hoje (no entanto, isso pode ser aceito pelo TS um dia, certo?).

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)

Eu sei que as chances de oferecer suporte a esse recurso são pequenas. Mas eu quero confirmar que esta não é uma história parecida com a digitação structural vs nominal .

A verificação atual é que você deve retornar algo de seu decorador que pode ser atribuído ao destino. a parte que falta é que não estamos capturando nenhum acréscimo ao tipo. Não acho que tenhamos uma ideia de como fazer isso, mas também não pensamos muito sobre isso. então eu acho que é uma sugestão justa, mas o custo para implementá-la pode ser muito alto.

Idealmente, acho que o tipo retornado pelo decorador deve ser idêntico ao destino. Não há como dizer se você usará o tipo decorado em uma posição de origem ou de destino em uma atribuição.

Embora eu ache que realmente devamos mesclar o tipo de retorno do decorador e o destino, como um mixin.

Apenas alguma ideia de funcionalidade adicional. Como a saída do compilador TypeScript é JavaScript, existem algumas linguagens semelhantes para Java, como Xtend

Você pode aprender sobre algumas idéias interessantes neste projeto. Procure por anotações ativas "As anotações ativas permitem que os desenvolvedores participem do processo de tradução do código-fonte Xtend para o código Java via biblioteca"

Controlar a saída do TypeScript por meio de decoradores será um recurso muito bom!

Devem ser possíveis decoradores para interfaces.

@shumy O problema é que as interfaces não existem em tempo de execução de forma alguma, e os decoradores são executados puramente em tempo de execução.

Existe uma razão para tratar os decoradores de propriedade de maneira diferente dos decoradores de método com relação ao valor de retorno? (Babel não).

O que quero dizer é que, se eu quiser definir a funcionalidade de uma propriedade por meio do decorador, tenho que fazer o seguinte:

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

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

Considerando que Babel exige que o descritor seja retornado:

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

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

Isso torna difícil escrever um decorador em TypeScript que faça parte de uma biblioteca que será usada a partir do Babel.

Eu proporia que um decorador de propriedade seja tratado de forma idêntica ao decorador de método, e o valor de retorno do decorador deve ser aplicado por meio de Object.defineProperty . Isso também permitiria vários decoradores em uma única propriedade, como um método.

Uma solução alternativa por enquanto (para compatibilidade com o Babel) é retornar o descritor do decorador, além de definir a propriedade, mas isso parece desnecessariamente um desperdício:

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

@mhegazy , por acaso você tem alguma ideia sobre o meu comentário acima?

@sccolbert No TypeScript optamos por não permitir o uso de descritores de propriedade para decoradores em propriedades de dados, pois isso pode levar a problemas em tempo de execução devido ao fato de que qualquer "valor" especificado pelo descritor será definido no protótipo e não na instância . Embora o exemplo acima geralmente não seja um problema, considere o seguinte:

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 uma proposta de uma versão futura do ES para suportar uma propriedade "inicializador" no descritor, que seria avaliada durante o construtor. Isso daria a você a capacidade de controlar se o valor é definido no protótipo ou alocado por instância.

Por enquanto, você pode contornar essa limitação chamando Object.defineProperty diretamente, conforme o exemplo em seu comentário. Continuaremos investigando se devemos relaxar essa restrição no futuro.

@rbuckton obrigado! Vocês estão conversando com o Babel para convergir na mesma semântica? Acho que isso é o mais importante.

Por que seria útil usar um decorador para especificar um valor para uma instância particular? Não é para isso que serve o inicializador de propriedade?

Meu caso de uso é mais complicado que o exemplo. Na verdade, estou usando o decorador para definir um getter que retorna um objeto vinculado ao contexto this da propriedade, de modo que os métodos desse objeto tenham acesso à instância na qual ele foi definido.

Você pode pensar nisso como uma emulação do protocolo descritor em Python, em que o método de acesso bar definido na classe Foo por meio da instância foo (ou seja, foo.bar ) invoca o __get__ método da função que retorna BoundMethod . Quando esse objeto é chamado, a função subjacente é chamada com self como o primeiro argumento, que neste caso é foo . Javascript não tem esse conceito, e é por isso que temos que passar thisArg e chamar function.bind todo lugar.

No meu caso, não estou definindo métodos, mas um sinal de tipo seguro. Aqui está o decorador:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L144

Quando a propriedade é acessada, ela retorna uma implementação de ISignal que está ligada ao contexto this do proprietário. Isso permite que o sinal se refira à instância em que foi definido:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L263

Na verdade, estou usando o decorador como um atalho para este equivalente detalhado:

class Foo {
  valueChanged: ISignal<number>;
}

defineSignal(Foo.prototype, 'valueChanged');

@rbuckton

Decoradores não são permitidos quando almejando ES3

Porque? Não consigo ver nada que impeça o uso de __decorate no ES3.

Ele usa o descritor de propriedade, que não está disponível no ES3
Le 4 set. 2015 14h03, "Koloto" [email protected] a écrit:

@rbuckton https://github.com/rbuckton

Decoradores não são permitidos quando almejando ES3

Porque? Não consigo ver nada que impeça o uso de __decorate no ES3.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/2249#issuecomment -137717517
.

@sccolbert Você pode fazer melhor com proxies ES6 em vez de decoradores de propriedade.

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

Os navegadores

@cybrown Sim, ele usa o descritor de propriedade para métodos e acessadores. Eu testei apenas em propriedades simples (campos sem acessadores). Mas parece que decoradores podem ser permitidos no ES3 para propriedades (sem acessores) e classes. Isso ajudaria.

E um descritor de propriedade falso pode ser usado para métodos ao direcionar o ES3. Algo como { writable: true, enumerable: true, configurable: true } . Portanto, não vejo razão para não oferecer suporte ao ES3.

@sccolbert entendo. Isso faz sentido. A propósito, o TypeScript está trabalhando em _esta_ digitação aprimorada para funções e métodos. Eu me pergunto se isso seria de alguma ajuda aqui. Embora eu suponha que digitar não seja o problema para você, é a semântica do tempo de execução.

@JsonFreeman _esta_ digitação melhorada parece intrigante para alguns dos meus outros casos de uso. Você tem mais informações sobre isso?

Acho que a discussão mais desenvolvida sobre _esta_ digitação está em # 3694.

Saúde!

Erro TS1207: Decoradores não podem ser aplicados a vários acessadores get / set com o mesmo nome.

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

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

o código acima não é permitido, mesmo que faça mais sentido comparar com

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

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

Getter e setter são definidos em uma chamada para Obect.defineProperty. Esta é uma peculiaridade do js, ​​a declaração de set e get embora separados, na verdade são a mesma declaração de propriedade. A verificação de erro no compilador serve para alertar os usuários ao pensar neles separadamente; os decoradores são aplicados apenas uma vez ao descritor de propriedade.

apenas imaginando, já que o compilador pode detectar get e set com o mesmo nome e combinar em um único Object.defineProperty, por que não combinar também o decorador?
ou talvez um sinalizador de opção para dizer ao compilador para combiná-los e deixar uma mensagem de aviso em vez de lançar um erro.

obrigada

@TakoLittle : A razão de não fazermos isso hoje deriva parcialmente de como os decoradores são compostos. Os decoradores seguem os mesmos princípios da composição da função matemática, onde (_f_ ∘ _g _) (_ x_) é composto como _f _ (_ g _ (_ x_)). No mesmo sentido, pode-se pensar que:

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

É aproximadamente:

F(G(X))

A composicionalidade dos decoradores é quebrada quando você decora o getter e o setter:

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

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

Como F e G compõem aqui? É baseado puramente na ordem do documento (ou seja, F(G(X)) )? Cada conjunto de decoradores para o getter e o setter são discretos e, em seguida, executados na ordem do documento (ou seja, G(F(X)) )? get e set implicam em alguma ordem específica (isto é, get sempre antes de set ou vice-versa)? Até estarmos 100% certos de que a abordagem mais consistente que não surpreende os usuários, ou ter uma abordagem bem documentada que faz parte da proposta dos decoradores com pelo menos estágio 2 ou melhor aceitação no ECMA-262, achamos que é melhor ser mais restritivo e com erro aqui, pois nos permite relaxar essa restrição em uma data posterior, sem introduzir uma alteração significativa que poderia facilmente passar despercebida e possivelmente resultar em comportamentos inesperados em tempo de execução.

@rbuckton muito obrigado pela explicação detalhada
Bom trabalho da equipe TS !! ^^ d

Onde está a documentação para isso? e cuidado para vincular o compromisso de implementação?

Obrigado.

@mhegazy Qual é o status da implementação da versão mais recente da especificação. Eu entendo que há algumas mudanças aí.

Este problema acompanhou a versão original da proposta. uma vez que isso foi concluído, estamos encerrando este problema. para quaisquer atualizações nas especificações, registraremos novos problemas e descreveremos todas as alterações recentes. Não acho que a proposta esteja em um lugar agora para estarmos prontos para que possamos pulá-la. Estamos trabalhando em estreita colaboração com @wycats na nova proposta.

@mhegazy Obrigado pela atualização. Eu adoraria me manter informado. Quando você criar o novo problema para a atualização das especificações, vincule-o aqui para que eu possa ser notificado e seguir em frente. A comunidade Aurelia faz uso intenso de decoradores e queremos sincronizar com TypeScript e Babel na atualização. Mais uma vez, obrigado pelo excelente trabalho que a equipe do TS está fazendo!

A decoração da função é necessária, claro.
Também existem planos para decorar outros objetos no código?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

Antony-Jones picture Antony-Jones  ·  3Comentários

siddjain picture siddjain  ·  3Comentários

uber5001 picture uber5001  ·  3Comentários

Zlatkovsky picture Zlatkovsky  ·  3Comentários

bgrieder picture bgrieder  ·  3Comentários