Typescript: Декораторы

Созданный на 7 мар. 2015  ·  139Комментарии  ·  Источник: microsoft/TypeScript

Предложение ES7

Предложение ES7 для декораторов можно найти здесь: https://github.com/wycats/javascript-decorators
Предложение ES7 служит основой этого предложения. Ниже приведены примечания о том, как система типов

Цели декоратора:

Конструктор класса

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

десахары для:

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

Методы

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

десахары для:

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

Статический метод

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

десахары для:

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

Характеристики

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

десахары для:

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

Формальный параметр метода / средства доступа

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

десахары для:

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

Где __decorate определяется как:

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

Подписи декораторов:

Допустимый декоратор должен быть:

  1. Присваивается одному из типов декораторов (ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator), как описано ниже.
  2. Верните значение (в случае декораторов классов и декораторов методов), которое можно присвоить декорированному значению.
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;

Примечания:

  • Украшение объявления функции не допускается, так как это заблокирует подъем функции в верхнюю часть области видимости, что является значительным изменением семантики.
  • Украшение функциональных выражений и стрелочных функций не поддерживается. Тот же эффект можно получить, применив функцию декоратора как var x = dec(function () { });
  • Декорирование формальных параметров функции в настоящее время не входит в предложение ES7.
  • При ориентации на ES3 нельзя использовать декораторы.
Committed ES Next Fixed Suggestion

Самый полезный комментарий

Конечно, функциональное украшение необходимо.
Есть ли планы по декорированию других объектов в коде?

Все 139 Комментарий

Простите за то, что я понимаю в спецификации, мы не сможем сделать:

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

Я прав ?

Как сериализация типов работает с остальными аргументами?

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

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

Использование декораторов кажется достаточно простым, но я обнаружил, что разделы, посвященные их объявлению, сбивают с толку. C.4 говорит, что декораторы должны быть аннотированы с помощью @decorator , но ни один из примеров не показывает, что это действительно происходит.

Предполагается ли, что фабрики декораторов быть классами, реализующими интерфейсы, найденные в B?

Каково правило уточнения интерпретации CoverMemberExpressionSquareBracketsAndComputedPropertyName?

Я заметил, что во многих типах в разных точках есть Function | Object , но они будут вырождаться в Object во время проверки типа. В чем причина наличия функции там?

Я не без ума от терминов DecoratorFunction и DecoratorFactory. Я бы предпочел следовать номенклатуре генераторов, в которой есть Generator и GeneratorFunction. С помощью этой схемы мы бы переименовали DecoratorFunction в Decorator, а DecoratorFactory в DecoratorFunction.

Для декорированного экспорта для чего нужен [lookahead ≠ @] ? Могут ли HoistableDeclaration и ClassDeclaration начинаться с @ ?

Это дублирование # 1557

На самом деле это не обман, поскольку № 1557 был для другого дизайна. Этот вопрос касается разрабатываемого дизайна декораторов.

Виноват.

Для декоратора в выражении функции не могли бы мы сделать что-то вроде:

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

преобразован в:

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

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

Некоторых немного беспокоит необходимость исправлять каждую функцию, например:

const myFunc = function () {}

Вы теряете подъем, и function.name

Реализация добавлена ​​в PR # 2399

Обновления: предложение обновлено, и добавлена ​​ссылка на декораторы JavaScript @wycats ES7.

опечалился, что это стало классной вещью ...
И что насчет амбиантного декоратора они вылезли из поля зрения?

@fdecampredon с вашим предложением функций, похоже, вы все еще теряете подъем.

@JsonFreeman почему? если вставить _t в начало файла?

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

Кроме того, даже если в моем предложении много проблем, я серьезно хотел бы иметь возможность использовать декораторы для функции (даже если мне придется использовать функцию, назначенную переменной) и потерять подъем.
Случай, подобный этой сути, кажется довольно хорошим вариантом использования декоратора как для функции, так и для класса (особенно в сочетании с окружающими декораторами, если они все еще находятся в области видимости)

@fdecampredon это не работает в общем случае, поскольку декораторы сами являются выражениями. например

myFunc();  // assumes function declaration is hoisted

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

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

если вы поднимете объявление функции и приложение декоратора, вы сломаете декоратор. если вы поднимаете только объявление функции, но не приложение-декоратор, вы можете наблюдать функцию в недекорированном состоянии. здесь нет привлекательных решений.

это та же проблема, что и с предложением расширения объявления класса, которое в ES6 является выражением. результатом было не объявление класса, а только символ (сродни объявлению var, но не объявлениям функций)

Упс не подумал об этом, спасибо @mhegazy.
Однако, почему функциональная часть полностью отказалась от исходного предложения @jonathandturner, было правило:

decorated function declarations cannot be hoisted to the containing scope

Потеря подъема - это, безусловно, недостаток, но я считаю опасным преобразовать его в функцию, предназначенную только для класса, когда у нее будет вариант использования для другой конструкции.

Давайте посмотрим, что подразумевается под желаемым набором ограничений:

  • украшенная функция должна быть поднята
  • функция должна быть оформлена, как только она станет доступной - поэтому приложение-декоратор должно быть поднято
  • декоратор должен быть определен до его применения - поэтому необходимо поднять само определение декоратора (или все сущности, на которые ссылается выражение декоратора)
  • выражение декоратора оценивается в том месте, где оно применяется лексически, поэтому приложение не может быть поднято

Единственное решение, которое я вижу для этого, следующее: для декораторов функций мы разрешаем только что-то в форме @identifier . Мы не допускаем выражения левой стороны. Кроме того, идентификатор должен быть прямой ссылкой на объявление функции (включая декорированную функцию). Все функциональные декорации, которые имеют место в области видимости, должны генерироваться в верхней части области в том порядке, в котором они были применены.

Проблема нарушения правил подъема в том, что это удивительно. Если вы какое-то время пишете javascript, вы ожидаете, что объявления функций будут доступны до их лексического объявления; теперь, добавляя, казалось бы, простой синтаксический маркер (декоратор), этот фундаментальный характер объявления функции изменяется.

При этом предложение ES7 все еще находится на начальной стадии, поэтому я ожидаю, что оно будет развиваться и расширяться; поэтому вполне возможно, что окончательное предложение будет включать функции в той или иной форме.

Я считаю, что их нужно поднять. Я говорю, что единственные украшения, которые мы разрешаем для функций, - это украшения, которые определенно сами поднимаются. А именно идентификаторы, которые ссылаются на объявления функций.

Но здесь есть другая проблема. На самом деле невозможно одновременно поднять все украшенные функции и гарантировать, что они не соблюдаются в их недекорированном состоянии. Проблему можно увидеть с циклом декоратора.

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

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

Даже если поднять обе функции, какая из них будет оформлена первой? Если dec2 будет оформлен первым, то dec1 не будет сам украшен к тому времени, когда он будет применен к dec2.

Таким образом, нам пришлось бы выбирать между следующим:

  • Функции не подняты
  • Неукрашенные функции поднимаются, но приложения-декораторы не поднимаются вместе с ними.

Хотя мне ни один из них не нравится, я даже не думаю, что возможно что-то еще.

Это предложение JS. поэтому движок не будет знать, относится ли выражение к объявлению функции или нет, хотя мы могли бы сказать с помощью статического анализа. учти это:

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

dec2 = undefined;

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

Фу! Javascript больше не становится проще. :-)

Было бы проще включить его только в функции _expressions_:

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

Функциональные выражения или лямбда-выражения не поднимаются.

Возможно ли иметь такие параметры в декораторе в машинописном тексте 1.5.0-alpha?

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

Хорошо, неважно, просто создайте фабрику, которая принимает параметры и возвращает фактическую функцию декоратора.

Например, декоратор класса со строковым параметром:

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

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

}

Приветствую.

Я пытаюсь понять, как написать декоратор, который будет подбирать типы, объявленные в конструкторе.

вот код, который иллюстрирует то, что я пытаюсь сделать, но он зашит. мне нужно, чтобы он ответил на объявление конструктора в классе 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;
}

Боюсь, что информация о типе выходит за рамки функции декоратора.
Вы можете использовать ParameterDecorator для каждого параметра, чтобы добавить такую ​​информацию.

Типизация или реализация ParameterDecorator не совсем корректны, из типизации целью является функция, но при использовании машинописного текста это объект-прототип.
Я должен привести целевой параметр, чтобы получить правильный тип:

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

Вместо того:

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

вау - правило декораторов параметров !!!!!!!!!!!!!!! посмотреть это репо

вот результат выполнения тестов:

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

и код :

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

Я сделал оболочку вокруг экспресс (но может поддерживаться любая веб-платформа, интерфейс адаптера определен) с API на основе декоратора: https://github.com/cybrown/web-decorators

Я использую ClassDecorator, ParameterDecorator и MethodDecorator.

@cybrown Я только что обновил подпись для ParameterDecorator в # 2635, которая теперь находится в master.

@rbuckton Спасибо, я обновлю это завтра в своем проекте.

Возможно ли иметь имя параметра в ParameterDecorator?
Это может быть полезно, потому что я думаю, что имя параметра может быть в половине случаев первым аргументом ParameterDecorator.
Более того, это могло бы быть решением проблемы искажения имени параметра минификаторами.

меня попросили сделать это:

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

но без указания ключей в декораторе инъекции, а вместо этого декораторы автоматически подбирают функции класса конструктора с чем-то вроде этих строк:

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

Я ищу, как сделать то, что здесь описывает @jonathandturner

@cmichaelgraham Да, наличие информации о типе среды выполнения (rtti, как описано в AtScript) было бы замечательно для такого использования, даже в полностью отдельной функции, такой как интроспекция.

@cmichaelgraham посмотрите https://github.com/Microsoft/TypeScript/pull/2589

@cmichaelgraham Мы работаем над предложением для ES7 добавить API метаданных, который будет работать вместе с декораторами. # 2589 добавляет экспериментальную поддержку этого предложения, но требует наличия отдельного полифилла для чтения метаданных.

@rbuckton @cmichaelgraham Изначально был разработан специальный декоратор TypeScript, который сообщал компилятору, что нужно вводить типы в декоратор в зависимости от декорируемой цели. Я думаю, это было @parameterTypes Правильно ли я полагаю, что эта функция удаляется в пользу более общего api метаданных? Если так, я бы это поддержал.

Не могли бы вы немного рассказать о статусе этого в отношении TypeScript. Это планируется к выпуску 1.5? Если да, то как будут выглядеть параметры компилятора? Было бы полезно, чтобы компилятор автоматически генерировал метаданные типа только для сигнатур конструктора.

Декораторы @EisenbergEffect являются частью 1.5, вы можете начать использовать их с нашей последней версией 1.5-alpha .

Что касается поддержки метаданных. Дизайн изменился с момента первоначального предложения для @paramtypes . новый дизайн использует предложение Reflect.metada. См. # 2589 для более подробной информации о том, как это работает. также у @rbuckton есть pollyfill для использования метаданных здесь: https://github.com/rbuckton/reflectDecorators

@mhegazy Я знаю это. Смотрите здесь: http://blog.durandal.io/2015/04/09/aurelia-update-with-decorators-ie9-and-more/ : smile:

Я также знаком с предложением метаданных. Я давал отзывы по этому поводу. Я просто хотел узнать, удаляется ли первоначальная идея @parameterTypes .

спасибо @EisenbergEffect за то, что поделился. : +1:

да. единственная проблема с @paramtypes заключается в том, что он делает вывод, управляемый системой типов и требующий глобальной информации о программе. Это нарушает сценарии одномодульной транспиляции (см. №2499). Другой вариант заключался в том, чтобы разместить его на сайтах для звонков, что добавляло много работы пользователям-декораторам, а не авторам. Поэтому мы вернулись к чертежной доске и вместо этого остановились на подходе Reflect.metadata.

Если вы также посмотрели на более ранние версии предложения, нам пришлось удалить декораторы окружающей среды / времени разработки по той же причине.

Спасибо за разъяснение. Все это имеет смысл. Есть идеи, появится ли подход, основанный на отражении, в 1.5?

да. в настоящее время он находится в мастерской. он должен быть доступен в следующем выпуске. в настоящее время это дополнительная функция с использованием экспериментального флага --emitDecoratorMetadata ; и он добавляет метаданные только к украшенным объектам.

«он только добавляет метаданные к украшенным объектам». Можете ли вы развить эту идею? Присутствует ли специальный декоратор? Или он к чему-нибудь прибавляет каким-нибудь декоратором? Другими словами, если разработчик использует декоратор Aurelia inject , то запустит ли это компилятор для генерации метаданных?

Если разработчик использует декоратор Aurelia inject, то запустит ли это компилятор для генерации метаданных?

да.

я имел в виду:

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

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

с --emitDecoratorMetadata компилятор выдаст метаданные типа (т.е. вызов reflect.metadata('desing:paramtypes', [Number, String]) ) для Foo но _not_ Bar .

Это сработает для нас. Мы подготовим поддержку Reflect.metadata api для нашего следующего выпуска, скорее всего, на следующей неделе. Спасибо за разъяснения!

так что для этого испускается?

язык = javascript
@inject
class Foo {
конструктор (a: A, b: B) {}
}

class Bar {
конструктор (a: число, b: B) {}
}

would it be (for Foo):

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

@cmichaelgraham Да, это примерно то, что генерируется.

безумно круто !!!!

использование gulp-typescript для создания этого репо - (хорошая работа @ivogabe)

gulpfile build-ts команда (обратите внимание на параметр 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('.'))
    ]);
});

превращается в 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' }
      ]);
    });
  }
}

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

Особый интерес представляют метаданные типа emiited о параметрах конструктора:

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

поэтому теоретически вы можете изменить функцию декоратора инъекции, чтобы внедрить правильные классы, не указывая их в инъекции, а вместо этого указывая типы в конструкторе :)

@cmichaelgraham Мы можем легко включить это сегодня, не меняя структуру. В библиотеке DI от Aurelia есть перехватчик container.addParameterInfoLocator . Вы передаете ему функцию, которая может принимать тип и возвращать его зависимости. В нашем следующем выпуске (на следующей неделе) мы можем добавить это в основную конфигурацию, чтобы разработчикам TypeScript было легко включить его. Я бы поместил это в выпуск на этой неделе, но я еще не понял, что это было изменено.

@EisenbergEffect блестящий !! : +1:

Я использовал аннотации, чтобы пометить свойства объекта как наблюдаемые, что преобразует примитив в наблюдаемый объект (для заинтересованных https://github.com/mweststrate/MOBservable/commit/8cc7fc0e20c000db660037c8b5c9d944fe4155d9).

Однако, особенно для свойств, было немного неестественно, что аннотации применяются к прототипу, а не в конструкторе самого класса, это означает, что трудно получить либо начальное значение, либо даже this . Для этого нужно было создать свойство, и в его геттере / установщике выполнить фактическую логику, которую должна была выполнять аннотация, поскольку тогда доступны this и значение.

Кроме того, отличная функция!

Используя Metadata Reflection API, я смог выполнить простую инъекцию зависимостей свойств.
Вы можете проверить необходимые изменения здесь https://github.com/matjaz/property-DI/commit/2b4835e100b72d954b57d0e656ea524539ac17eb.

Не должно быть декоратора __metadata в сгенерированном коде, вызываемого в первую очередь декораторами? Я хотел создать простой деократор, используя метаданные класса, но __metadata ('design: paramtypes', [TYPES ....]) вызывается последним, поэтому я не могу получить эти данные из Reflect.

@ufon, можете ли вы поделиться кодом, который вы смотрите?

Конечно, здесь https://gist.github.com/ufon/5a2fa2481ac412117532

РЕДАКТИРОВАТЬ:
мой плохой, в моем коде есть другая ошибка, не могу получить типы даже после инициализации класса

@ufon , я не уверен, что вижу проблему. __metadata - это последний элемент в списке декораторов, это означает, что он выполняется первым, и определенно до того, как будет выполнено inject.

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

где __decorate определение использует право уменьшения для выполнения декораторов в обратном порядке.

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

Это должно соответствовать спецификации, что декораторы (как выражения) оцениваются в порядке объявления, но выполняются в обратном порядке, позволяя внешним декораторам получать результаты от внутренних декораторов.

Понятно, извините за досаду :) плохо, в моем коде есть другая ошибка, не могу получить типы даже после инициализации класса

как вы их запрашиваете? а у вас есть поллифил для Reflect.metadata?

добавил к сути ...
Reflect.getMetadataKeys (Дом);
это приводит к пустому массиву ..

Да, мне нужен пакет "Reflection-metadata"

похоже, что полифилл теряет его, @rbuckton можешь взглянуть.

Думаю, я понял ...
требования перемещаются после вспомогательного кода машинописного текста

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

Итак, со временем var __metadata инициализируется, полифилл еще не загружен

но я не могу получить их до сгенерированного кода

РЕДАКТИРОВАТЬ: обновил суть ... когда один модуль скомпилирован, нет возможности загрузить полифилл до разрешения __metadata, потому что сгенерированный код машинописного текста всегда перемещается в верхнюю часть файла

ох .. это большая проблема. зарегистрированную проблему № 2811, чтобы отследить ее

А пока вам потребуется reflect-metadata в другом модуле (вам нужно два файла).
см. https://github.com/matjaz/property-DI/

Похоже, существует несогласованность вызовов, что затрудняет разработку декораторов с переменными / необязательными параметрами. В качестве примера:

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

@F()
prop: number;

В первом случае F вызывается с параметрами (target, propertyName, propertyDescriptor), а во втором F вызывается с параметрами () и требует, чтобы вы вернули внутреннюю функцию, которая, в свою очередь, вызывается с (target, propertyName и propertyDescriptor).

Мое _ предположение_ заключалось бы в том, что и @F, и @F () будут эквивалентны, и поэтому вы можете выполнить любой вызов только в том случае, если декоратор поддерживает 0 или более параметров.

Это особенно важно в случае:

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

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

Это можно обойти, просто явно вызвав @F () вместо @F, но легко допустить эту ошибку, а затем запутаться, когда ваш декоратор не работает, хотя, судя по всему, он должен.

В этом случае я бы хотел сделать следующее:

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

и покончить с этим, но вместо этого я должен сделать что-то вроде следующего грубого примера:

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

Это настоящая боль.

@Tharaxis такое поведение @F и @F() не эквивалентны. @F вызывает декоратор F , где @F() вызывает фактор декоратора F с пустым списком аргументов, а затем применяет получившийся декоратор.

Проблема здесь в том, что компилятор сравнивает сигнатуру фабрики декораторов F с declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; которая оказывается назначаемой. Я думаю, что нам нужна более строгая проверка, чтобы убедиться, что мы нашли эту проблему. Я зарегистрировал номер 3246, чтобы исправить это. Я считаю, что вы поступаете правильно, и компилятор должен отловить недопустимое использование фабрики декораторов.

@mhegazy Я не уверен, почему это так, однако существует ли какой-либо вариант использования, который исключает возможность создания эквивалентов @F и @F() , поскольку в конечном итоге они оба являются идентичным вызовом функции декоратора , только один из них также включает вызов внешней фабричной функции? Похоже, здесь нарушается принцип наименьшего удивления, поскольку это определенно довольно удивительное поведение, и я не понимаю, почему должно быть два способа вызова декоратора с точки зрения потребителя API.

Хотя я понимаю, что с точки зрения JS есть разница (в одном вы передаете ссылку на функцию F, а в другом вы выполняете функцию, а затем передаете ссылку на ее результат в качестве декоратора), я не конечно, я понимаю, почему нельзя просто иметь декораторы, которые передаются как @F неявно сопоставляют (и вызывают) фабрику как один с пустым списком параметров?

Компилятор не может узнать, фабрика это или декоратор. если у вас есть эта подпись:

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

Это декоратор или это фабрика, вызывать ее с пустыми аргументами или нет?

Максимум, что может сделать компилятор, - это проверить соответствие подписи при ее вызове тем или иным способом и выдать ошибки, если это не так.

В @rbuckton есть исправление для # 3246, которое должно

@mhegazy , я говорю, что использование декоратора (т.е. @F или @F() ) должно _всегда_ быть вызовом фабричной функции F, которая, в свою очередь, возвращает соответствующий тип декоратора. Не существует представления декоратора на верхнем уровне без инкапсулирующей фабрики.

Другими словами, подпись F всегда:

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

Или какой-то эквивалент.

Затем вызов @F становится эквивалентом @F() компилятором. Поскольку обе сигнатуры @F и @F() должны в той или иной форме соответствовать фабричной сигнатуре, это решает проблему путаницы при вызове. Есть только один способ вызвать декоратор!

Учитывая следующее:

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

@F()
property: number;

а также

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

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

Они делают то же самое, но я должен объявить их по-другому! Однако объявление F таким образом, чтобы он мог существовать в обоих направлениях, действительно обременительно и делает невозможным создание согласованных параметров и документации. Вы либо выбираете, должен ли ваш декоратор использовать фабрику, либо нет, и ваш потребитель API должен знать это различие заранее, чтобы использовать его.

@Tharaxis Если я смотрю на библиотеку, предоставляющую F которая будет использоваться в качестве декоратора, я абсолютно ожидаю, что @F и @F() будут разными вещами. Точно так же, как я ожидал, что C и C() будут разными вещами - первое ссылается на значение, второе вызывает значение. @F применяет декоратор, @F() вызывает функцию, которая создаст декоратор. Это не одна и та же операция, и не должно быть.

@DavidSouther есть ли веская причина, по которой этого не может или не должно быть? Нет функциональной разницы между вызовом @F и @F() но есть довольно большая разница между тем, как они определены и документированы, и это добавляет дополнительную сложность к вызову, в которой не должно быть необходимости.

Мое предложение состоит в том, что не должно быть двух способов определения декоратора, потому что в этом нет необходимости, и не должно быть необходимости определять декоратор более чем одним способом.

Нет функциональной разницы между вызовом @F и @F ()

Думаю, это главный предмет разногласий. я не думаю, что это правда. F - это ссылка на функцию, F () - это ссылка на возвращаемое значение F при вызове с пустым набором аргументов (т.е. F.call(this, []) ); а это две функционально и концептуально разные вещи.

@Tharaxis @F и @F() - разные вещи. Есть разница в том, как они используются, различие в том, как они документированы, и различия в их вызовах, что абсолютно необходимо.

Позвольте мне задать другой вопрос: почему каждая функция-декоратор должна быть фабрикой? Используя ваше предложение, невозможно было бы иметь простого декоратора.

@mhegazy, хотя я @F() ) действительно приводит к генерации функционального закрытия и дополнительных накладных расходов за счет создания изолированных (и дублированных) функций-декораторов и вызова @F по существу работает с ссылкой на общую функцию-декоратор, я бы сказал, что изоляция посредством замыкания будет «более безопасной» и последовательной, уменьшая вероятность неожиданностей.

@DavidSouther Ваш вопрос зависит от того, что вы определяете как простой декоратор. Ничто не мешает иметь простой декоратор в виде:

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

Я нахожу это довольно минимально инвазивным, поскольку он всего на 2 строки больше, чем «простой синтаксис», и более четко определяет доступные вызовы декораторов. Кроме того, поскольку тогда существует только один способ определить декоратор (и давайте не будем сбрасывать со счетов значение согласованности), вам, вероятно, потребуется еще примерно 2 секунды, чтобы реализовать это, по сравнению с «простым синтаксисом».

Кроме того, существует более проблемная проблема:

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;

Одна из этих вещей не похожа на другую. Использование @F не будет работать, хотя по номинальной стоимости определенно должно. Имейте в виду, представьте, что я тот, кто не знает, как выглядит базовое объявление F, но вместо этого просто знаю, что F существует и может принимать необязательный набор аргументов. Шансы на то, что я ошибаюсь с @F , не являются тривиальными при текущем механизме. Это возлагает большую ответственность на разработчика / составителя документации, чтобы убедиться, что потребитель знает, что @F не будет работать (но, возможно, @C будет работать, потому что это каким-то образом "другое").

Если мне нужен декоратор «один размер для всех», о котором потребителю не нужно беспокоиться, я должен сделать эту ужасную вещь:

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

Что откровенно ужасно. Кроме того, я теряю все ценные параметры intellisense для декоратора F, поскольку теперь его нужно обобщить.

Можно многое сказать о том, чтобы декораторы можно было легко и последовательно определять и документировать. Давайте не будем обманывать себя, полагая, что каждый, использующий наш код, будет иметь такую ​​же смекалку или понимание, как и мы, создатели этого кода.

@Tharaxis Я думал об этом на раннем этапе проектирования декораторов и понимаю ваши особенности. Как ранее упоминали @DavidSouther в этом потоке, такое поведение согласуется со стандартным поведением функций в JavaScript.

Вы правы в том, что любая попытка создать функцию, которая может действовать как декоратор и как фабрика декораторов, может быть несколько сложной. В целом, тем не менее, может быть лучше использовать передовой метод - всегда использовать фабрики декораторов и предоставлять правила для линтера, чтобы гарантировать, что эта практика будет принята.

Кроме того, я только что отправил PR № 3249, чтобы ответить на ваш предыдущий вопрос относительно несоответствий в проверке типов для декораторов.

Привет @rbuckton!

Я один из тех не очень опытных пользователей декораторов, упомянутых @Tharaxis ...

Думаю, у меня возник бы вопрос: должны ли декораторы соответствовать функциям? Т.е. в случае функций возврат this.f вместо this.f () имеет для меня полный смысл, поскольку иногда мне нужно значение, иногда мне нужна вещь, которая производит значение.

В случае с декораторами мне кажется, что на уровне языковых функций все не так четко. Я хотел бы возразить, что я просто хочу применить декоратор @F , я действительно не хочу знать, был ли он реализован как фабричный или статический метод. Это если я не потеряю некоторые возможности, которые иначе были бы у декораторов.

Приношу свои извинения, если вышеизложенное неверно или невежественно, я относительно новичок в мире javascript.

Спасибо и ура

Я также должен задать этот вопрос - имеет ли смысл поддерживать синтаксический паритет с функциями _ если_ (и только если) для этого нет реальной причины, кроме «потому что это определено с помощью функций». Я считаю, что декораторы совершенно не связаны с функциями, которые просто _happen_ должны быть определены с их помощью.

Сейчас тот факт, что @F и @F() не идентичны, вызывает следующие проблемы:

  • Разработчикам необходимо понимать разницу между @F и @F() и почему иногда это может работать и почему иногда это может не работать (и необходимость использования @F сравнению с @F() может быть полностью специфичным для платформы / кода / использования - поэтому несовместимо с точки зрения потребления: представьте, что declare function A(params?: any): ParameterDecorator vs declare function B(target, name) , @A не будет работать, но @A() будет, @B будет работать, но @B() - нет, но когнитивно это одно и то же, применение декоратора без аргументов).
  • Решение проблемы на уровне кода требует сложного обходного пути, который лишает человека возможности адекватно документировать синтаксис вызова декоратора.
  • Рефакторинг - это немного больше работы, если вы создали «необработанный декоратор» ( @F ) и теперь хотите добавить некоторые параметры (но сделать их необязательными для обратной совместимости). В соответствии с текущим методом все эти примененные @F теперь нужно будет реорганизовать до @F() - некоторые в коде, к которому у вас может не быть доступа, - в то время как неявный вызов позволит вам сохранить работу и ограничить измените свой код.

@Tharaxis Я понимаю, что различие между @F и @F() может стать большим когнитивным бременем для потребителей. Однако похоже, что вы просите, чтобы излучаемый код и, следовательно, поведение изменились, чтобы они вели себя одинаково. Скорее всего, это будет противоречить предложению ES7 для декораторов, поскольку маловероятно, что эти две формы будут обрабатываться одинаково. Не будет хорошо, если это приведет к расхождению.

Другой момент заключается в том, что путаница здесь сродни путанице между передачей функции как обратного вызова и вызовом функции. Если бы у вас было это

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

Пользователь может запутаться и вызвать takesCallback(callbackFactory) не вызывая callbackFactory . Это действительно похоже на путаницу, на которую вы указали с декораторами, но если бы язык нормализовал эти две формы, чтобы они делали одно и то же, была бы потеря выразительности. Иногда различие важно, и его нужно поддерживать. Не уверен, что это обычно относится и к декораторам, но теоретически, безусловно, может.

@JsonFreeman Я действительно не понимаю ваших аргументов. Вы говорите, что вы не можете сделать декораторы самостоятельными сущностями, потому что для декораторов существует предложение ES7, но в том-то и дело, декораторы ES7 - это просто _proposal_, и, насколько мне известно, они ' отчасти основаны на сочетании декораторов, описанных вами и Google, верно? Пока еще ничего не решено относительно того, как они будут работать, и вполне вероятно, что предложение пройдет _много_ итераций до утверждения, так почему бы не заставить их работать согласованно в TS, а затем (по крайней мере, попытаться) получить различные стороны, вовлеченные в предложение, стоящее за идеей, используя в качестве основы опыт, полученный в TS? Это будет не первый раз, когда TS реализует функцию только для того, чтобы позже пришлось провести рефакторинг, потому что спецификация ES6 требовала немного другого поведения. У вас _ всегда_ будет эта проблема, пока вы отслеживаете спецификацию, которая еще не существует и еще не согласована.

Что касается вашего примера с функциями, похоже, мы снова объединяем функции с декораторами, когда они на самом деле (и должны быть) совершенно разными конструкциями. Когда я работаю с функцией в JavaScript, я понимаю, как используются функции, и понимаю разницу между ссылкой на функцию и вызовом функции (и когда я могу использовать одну функцию по сравнению с другой) - и когда я что-то описываю как функция, нет никакой путаницы в том, что она использует. Декораторы _не_ функции и не должны наследовать поведение функций. Функции - это просто метод, на основе которого строятся декораторы. Это все равно, что сказать, что классы - это функции, а это не так, функции - это просто способ, которым мы описываем классы до ES6, потому что не было более очевидного доступного метода. Классы не берут на себя свойства функций, вы не можете вызывать классы, такие как функции, но вы объявляете их с помощью функций до ES6.

Тем не менее, я чувствую, что исчерпал свои аргументы по этому вопросу. Придется просто обойти выбор. В моем собственном коде я всегда буду использовать фабрики ради собственной согласованности. Я по-прежнему считаю, что отношение к декораторам буквально как к функциям может вызвать много разочарований в будущем, но это только я.

@mhegazy Это та же проблема, которую я поднял в репозитории декораторов https://github.com/jonathandturner/decorators/issues/16 Я действительно думаю, что это то, о чем следует подумать. В его нынешнем виде, я ожидаю, что этот выбор дизайна станет темой многих сообщений в блогах, которые тоже будут озаглавлены что-то похожее «Ловушки с декоратором ES7».

@Tharaxis , я согласен с вашим первым пунктом. Я не думаю, что предложение ES7 должно помешать нам что-то хорошо спроектировать, и в любом случае наш дизайн действительно является одним из предшественников ES7.

Что касается аргумента «декораторы не являются аргументом функций», то, если я не ошибаюсь, мы не обсуждаем сами декораторы, мы обсуждаем фабрики декораторов. И независимо от различий между декораторами и функциями, я думаю, что декораторы _factories_ на самом деле являются просто функциями. И похоже, что вы просите компилятор автоматически сгенерировать вызов фабрики декораторов, что является обычной функцией. Если бы это произошло, программист не смог бы отличить фабрику декоратора от декоратора.

Кроме того, обработка декораторов как функций хороша тем, что потребитель может применять их как декоратор или просто вызывая их по своему усмотрению.

@JsonFreeman аргумент вращается вокруг текущей ситуации, когда есть два способа как описать, так и вызвать декоратор, один через фабричную функцию, а второй как "сырую" функцию (которая в фабричном случае является результатом фабричного вызова ), и вопрос был больше о том, «не должен ли быть только один способ, а фабрики должны быть такими».

Я спрашиваю: да, не могли бы мы просто заставить компилятор превратить @F в эквивалент вызова @F() и сделать так, чтобы для проверки типов требовался список аргументов из 0 ... n параметров при использовании синтаксис без скобок. Возможно, вы могли бы уточнить, что имеется в виду под «... программист не сможет различить фабрику декораторов и декоратор ...», поскольку я думаю, что это очень легко отличить. Декоратор - это всегда ответ фабрики, а название фабрики - это имя декоратора ... не так уж и сложно, или я неправильно понял?

Что касается вашего последнего пункта о разрешении потребителю просто применять декоратор, если хорошо описано, что все декораторы используют фабрики, довольно легко затем вызвать декоратор самостоятельно, вы просто выполняете <decorator name>(<argument list>)(target, name) по сравнению с <decorator name>(target, name) в качестве примера. Имейте в виду, что обязательное использование фабрик будет означать, что первый пример всегда будет работать, в то время как отказ от него приведет к тому, что второй пример иногда не будет работать, и полностью зависит от того, как реализован декоратор - головная боль, которая только и ждет, чтобы случиться.

Я считаю необходимым отметить, что у меня нет проблем с декораторами, использующими функции, но моя проблема в том, что наличие двух способов описания одного и того же приводит к проблемам с согласованностью. Особенно, когда эти два способа также означают, что ваш метод вызова должен отличаться. Это несоответствие, которое мешает всему, от рефакторинга до согласованности языка.

Проблема рефакторинга, которую я описал несколькими сообщениями назад, уже должна быть более чем достаточной причиной, по которой текущий метод нуждается в проверке.

Привет всем, всего лишь последние 1,5 цента от обычного пользователя в соответствии с тем, что сказал @Tharaxis .

Я был бы более чем доволен текущим предложением, если:
а) Вселенная требует этого.
б) декораторы потеряли бы важные возможности, если бы все было иначе.

Последнее, конечно же, является оценочным суждением, ответ на который в некоторой степени будет зависеть от типа вашего разработчика (например, обычного пользователя / опытного пользователя и т. Д.). Я отношусь к первой категории, и я обычно разбросан по нескольким языкам и фреймворкам. Так что для меня «важные возможности» не будут включать гибкость для написания декораторов двумя разными способами. Примеры всех компромиссов были бы замечательными, если бы они существовали.

В идеале было бы здорово, если бы @F и @F () могли быть согласованными независимо от того, как реализованы декораторы, но в противном случае я бы предпочел ограничиваться использованием фабрик при написании декораторов, а не избегать граблей в траве. всякий раз, когда я их использую.

Спасибо и ура

Похоже, что этот запрос основан на идее, что фабрики декораторов - это канонический способ использования декораторов, но я не понимаю, как это позволяет пользователю определять и использовать необработанные декораторы. Если я определяю декоратор F , а приложение @F обрабатывается как @F() , тогда в качестве декоратора используется результат вызова F вместо самого F. Вы предлагаете, чтобы мы где-то выдавали ошибку, если кто-то пытается применить необработанный декоратор вместо фабрики декораторов?

Кажется, что эта идея полностью изменит естественную композицию декораторов и фабрик декораторов. Декораторы определенно ощущаются здесь как примитивная конструкция, а фабрики декораторов представляют собой слой абстракции, построенный поверх декораторов. Это всего лишь образец, не более того. Если бы вместо этого фабрика декораторов стала канонической вещью, примитивом, тогда люди определили бы кучу фабрик декораторов, которые не принимают аргументов и возвращают плоский декоратор. Это могло бы показаться очень глупым и перевернуло бы естественное интуитивное представление о том, что считается основным, а что - более сложным.

Одна вещь, которой я очень опасаюсь с декораторами, - это избыток магии. Я лично нервничаю, когда не понимаю, что делает код, и если компилятор тайно добавляет дополнительные вызовы, которые не писал программист, мне кажется, что это слишком вуду.

Привет @JsonFreeman!

Как я уже упоминал, я всегда предпочитаю, чтобы уродство было с автором декоратора, а не с пользователем. Однако я согласен с тем, что множество фабрик без аргументов - это ужасно. Можно ли это исправить с помощью декоратора? Например

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

Во всяком случае, если такой подход может быть найден разумным, он имеет то преимущество, что аннотация документирует цель функции. т.е. украшает.

Ваше здоровье

Хорошая подливка, декораторы, помогающие определить декораторы, серьезный случай Typeception.

@JsonFreeman Я не уверен, что наличие функций с нулевыми параметрами обязательно более уродливо, чем наличие вместо них множества функций (цель, имя). Во всяком случае, по крайней мере, функции с нулевыми параметрами обеспечивают уровень специфичности / явности, которого не хватает другому методу (поскольку параметры никогда не соответствуют вызову в последнем случае), и, кроме того, вместо этого предоставляет единственную цель для документирования ненужного уровня непоследовательности. Я также вижу нежелание идти по какому-либо одному маршруту из-за предполагаемой «возможной» сложности, но я бы сказал, что в свете очень реальной «очевидной» сложности, которую текущая реализация накладывает на сторону потребления, следует больше ошибаться на стороне очевидного, чем возможного.

Другой вариант - указать, что декораторы, вызываемые как @F , могут соответствовать либо шаблону для ClassDecorator, MethodDecorator, PropertyDecorator или ParameterDecorator, либо фабричной функции 0..n arg, которая возвращает ClassDecorator, MethodDecorator, PropertyDecorator или ParameterDecorator. Однако я бы чувствовал, что эта реализация вызовет другие ошибки (что, если у вас есть две конфликтующие функции, какая из них будет считаться наилучшим возможным соответствием?) И просто добавит чрезмерной сложности в компилятор. Простое преобразование вызовов @F в @F() было бы более простым решением и решило бы различные указанные проблемы.

Извините, чтобы уточнить, я не утверждал, что ваше решение является сложным. Я имел в виду, что автоматический вызов декоратора перед его применением непрозрачен. Вставленный вызов невидим для пользователя, и я чувствую, что многие пользователи этого не ожидают.

Я думаю, что нынешняя ситуация тоже не сложна. Как вы отметили, это позволяет авторам библиотек быть расплывчатыми или неопределенными в отношении того, предназначена ли их функция для применения в качестве декоратора. Это я вам предоставлю. Но хорошая библиотека прояснит это.

Что происходит в предложенной вами схеме, когда вы вызываете автоматически, что происходит, когда вы используете произвольное выражение декоратора вместо простого идентификатора? Это тоже вызывается?

Я согласен, что, если не указано иное, неявный вызов может вызвать удивление, но только для тех, кто не использовал такие концепции, как атрибуты C # и т.п.

Не могли бы вы пояснить, что вы подразумеваете под произвольным выражением декоратора?

Хорошая подливка, декораторы, помогающие определить декораторы, серьезный случай Typeception.

Честный звонок. Подходит для плохо продуманного комментария поздно вечером на выходных. Урок выучен. Интернет.undo ().

Я хочу сказать, что если окажется, что согласованный синтаксис сайта вызова требует использования фабрик, то я более чем доволен этим. Без сомнения напишу декоратора для снятия котельной плиты. Опять же, я бы предпочел небольшую боль при написании декоратора, а не повторяющуюся боль при их использовании (хотя на данный момент это потенциальная боль). Другие не согласятся.

Нет ли проблем и с улучшением API? Декоратор, созданный без фабрики, не может иметь дополнительные параметры, добавленные позже, поэтому я предсказываю @F и @F2() в моем будущем.

Я также не вижу, чтобы вы получили этот сценарий при использовании f и f() , это разные варианты использования на стороне потребления. В случае с декоратором я всегда применяю / вызываю декоратор к цели прямо тогда и там, и всегда за кулисами происходит вызов метода.

Но для меня проблема заключается в удобстве использования, когда я применяю декоратор, я не хочу, чтобы мне приходилось гуглить, как автор реализовал его - меня интересует поведение, а не упаковка.

Ваше здоровье

Просто последнее замечание, чтобы подвести итог, а потом я промолчу. Короче говоря, для меня это просто проблема дизайна взаимодействия. Мне очень хочется, чтобы декораторы создавались снаружи внутрь, а не наоборот.

Как пользователь UI / UX я вижу это довольно часто. Я работал с талантливыми разработчиками, которые предлагали решения пользовательского интерфейса, которые навредили бы пользователям (один пример - две кнопки сохранения в одном случае для решения сложности транзакции - опять же умный парень и хороший человек). Я просто думаю, что когда вы по уши в сложной логике деталей реализации, может быть трудно забыть то, что вы знаете, и увидеть это глазами обычного пользователя.

Теперь, если в среднем я просто ошибаюсь, или если сложность требует текущего дизайна, тогда все в порядке, мне просто нужно напрячь свой мозг и учиться.

Ваше здоровье

Если ваш декоратор является не идентификатором, а каким-то другим выражением, вы его вызовете автоматически? Например, допустим, это был декоратор. ИЛИ с выражением резервной функции. Что-то вроде:

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

Вы тогда автоматически вызываете это? Это даст вам неправильный результат, потому что вы в конечном итоге вызовете декоратор по умолчанию, прежде чем он будет применен:

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

Это не кажется правильным.

@JsonFreeman ,

Тем не менее, я полагаю, что единственный способ, которым это будет работать, будет, если само произвольное выражение оценивается с тем же синтаксисом фабрики, поэтому:

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

Я согласен, что это действительно приводит к тому, что вам придется добавить еще несколько неудобных битов вокруг вашего произвольного декоратора, но я думаю, что у одного есть более серьезные проблемы, чем добавление () => если вы используете произвольные выражения в качестве декораторов.

ПРИМЕЧАНИЕ. Я, очевидно, не имел в виду, что лямбда-синтаксис будет единственным способом объявить его, но () => немного лучше, чем function() { return function(target) { /*...*/ } } .

Очень жаль, что прерываю обсуждение синтаксиса вызова декоратора, но может ли кто-нибудь официально прояснить порядок вызова выражений декоратора? В частности, в соответствии с типом цели декоратора, положением целевого члена в исходном источнике и порядком декораторов в единственной цели.

@billccn Декораторы применяются снизу вверх. Итак, в исходном исходном коде, если у вас есть

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

Это сначала применит G к методу, а затем применит F к результату. Вы об этом спрашиваете?

Как насчет этого:

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

они следуют порядку области видимости, поэтому сначала все свойства / методы в порядке объявления, затем класс. т.е. B, C, D, E, F, A.

вы можете увидеть сгенерированный код здесь .

Кажется, что в настоящее время невозможно декорировать свойства параметров. Можно ли их поддержать?

Свойства параметров - это такие вещи, как constructor(private prop: Type) , где свойство (поле) и параметр объявляются вместе. Проблема в том, что они оба могут быть очищены, поэтому, возможно, придется придумать какой-то творческий синтаксис.

Вопрос: Можно ли использовать декораторы для изменения поведения метода простого объекта, например ...

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

... а если нет, то почему? Будет действительно полезно.

Пока нет, но мы рассматриваем это. @rbuckton может иметь более подробную информацию.

Могу я спросить, почему декоратор не может изменить декорируемый тип результата с декорируемого типа?

Например, в качестве альтернативы можно предложить MethodDecorator, как показано ниже:

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

Таким образом, декоратор может быть более гибким в использовании и работает как макрос. Тип декорированного класса / поля может быть обновлен соответственно типу, возвращаемому декоратором, так что безопасность типа по-прежнему сохраняется.

Я знаю, что https://github.com/jonathandturner/brainstorming/blob/master/README.md#c4 -defining-a-decorator заявил, что возвращаемый тип должен быть таким же. Но type validity здесь недопустимый аргумент, ИМХО. Во-первых, декораторы в JavaScript могут изменять тип класса / поля, поэтому проблем совместимости между TS и JS нет. Во-вторых, декораторы применяются статически, компилятор TS может определить исходный тип поля и тип декорированного поля на сайте определения.

@HerringtonDarkholme Я бы сказал, что это нужно отслеживать в другом предложении. Основная причина в том, что компилятор рассуждает о типах через объявления. после объявления типа он не может быть изменен. добавление мутаторов типов в микс означает, что нам нужно разрешить их, поскольку мы разрешаем объявление, которое может стать непростым.

@mhegazy , могу ли я понять это как компромисс для простоты реализации, а не как преднамеренный дизайн?

Согласно этому предложению декораторы восстанавливают возможность запуска кода во время разработки, сохраняя при этом декларативный синтаксис. Итак, следующий код должен быть
действителен в будущем JavaScript, но недействителен в сегодняшнем TypeScript (однако однажды это может быть принято TS, не так ли?).

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)

Я знаю, что шансы на поддержку этой функции невелики. Но я хочу подтвердить, что это не похожая история, как structural vs nominal typing.

Текущая проверка заключается в том, что вы должны вернуть из своего декоратора что-то, что можно назначить цели. недостающая часть заключается в том, что мы не фиксируем никаких дополнений к типу. Я не думаю, что у нас есть представление о том, как это сделать, но мы тоже об этом особо не думали. так что я думаю, что это справедливое предложение, но стоимость его реализации может быть очень высокой.

В идеале я думаю, что тип, возвращаемый декоратором, должен быть идентичен цели. Неизвестно, будете ли вы использовать декорированный тип в исходной или целевой позиции в задании.

Хотя я думаю, что на самом деле нам следует объединить возвращаемый тип декоратора и цель, что-то вроде миксина.

Просто идея дополнительной функциональности. Поскольку вывод компилятора TypeScript - это JavaScript, есть несколько похожих языков для Java, например Xtend.

Вы можете узнать о некоторых интересных идеях по этому проекту. Ищите активные аннотации «Активные аннотации позволяют разработчикам участвовать в процессе перевода исходного кода Xtend в код Java через библиотеку»

Управление выводом TypeScript через декораторы будет действительно приятной функцией!

Должны быть возможны декораторы для интерфейсов.

@shumy Проблема в том, что интерфейсы никоим образом не существуют во время выполнения, а декораторы работают только во время выполнения.

Есть ли причина рассматривать декораторы свойств иначе, чем декораторы методов, в отношении возвращаемого значения? (Бабель этого не делает).

Я имею в виду, что если я хочу определить функциональность свойства через декоратор, я должен сделать следующее:

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

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

В то время как Babel требует возврата дескриптора:

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

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

Это затрудняет написание декоратора на TypeScript, который является частью библиотеки, которая будет использоваться из Babel.

Я бы предложил, чтобы декоратор свойств обрабатывался так же, как декоратор метода, а возвращаемое значение декоратора должно применяться через Object.defineProperty . Это также позволит использовать несколько декораторов для одного свойства, как и для метода.

Обходной путь на данный момент (для совместимости с Babel) - вернуть дескриптор из декоратора в дополнение к установке свойства, но это кажется излишне расточительным:

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

@mhegazy не

@sccolbert В TypeScript мы решили запретить использование дескрипторов свойств для декораторов в свойствах данных, так как это может привести к проблемам во время выполнения из-за того, что любое «значение», указанное дескриптором, будет установлено в прототипе, а не в экземпляре. . Хотя в приведенном выше примере, как правило, не возникает проблем, примите во внимание следующее:

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

Есть предложение для будущей версии ES для поддержки свойства «инициализатор» в дескрипторе, которое будет оцениваться во время конструктора. Это даст вам возможность контролировать, устанавливается ли значение в прототипе или выделяется для каждого экземпляра.

На данный момент вы можете обойти это ограничение, вызвав Object.defineProperty напрямую, как показано в примере в вашем комментарии. Мы продолжим выяснять, следует ли ослабить это ограничение в будущем.

@rbuckton спасибо! Вы, ребята, ведете переговоры с Babel, чтобы прийти к одной и той же семантике? Я думаю, это самое главное.

Почему было бы полезно использовать декоратор, чтобы указать значение для конкретного экземпляра? Разве не для этого предназначен инициализатор свойств?

Мой вариант использования более сложен, чем пример. На самом деле я использую декоратор для определения получателя, который возвращает объект, привязанный к контексту свойства this , чтобы методы указанного объекта имели доступ к экземпляру, в котором он был определен.

Вы можете думать об этом как о эмуляции протокола дескриптора в Python, где доступ к методу bar определенному для класса Foo через экземпляр foo (т.е. foo.bar ), вызывает __get__ метод функции, которая возвращает BoundMethod . Когда вызывается этот объект, вызывается основная функция с self в качестве первого аргумента, которым в данном случае является foo . В Javascript нет этой концепции, поэтому мы должны передавать thisArg и вызывать function.bind повсюду.

В моем случае я определяю не методы, а типобезопасный сигнал. Вот декоратор:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L144

При доступе к свойству возвращается реализация ISignal которая привязана к контексту this владельца. Это позволяет сигналу обращаться к экземпляру, в котором он был определен:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L263

Фактически, я использую декоратор как ярлык для этого подробного эквивалента:

class Foo {
  valueChanged: ISignal<number>;
}

defineSignal(Foo.prototype, 'valueChanged');

@rbuckton

При ориентации на ES3 нельзя использовать декораторы.

Почему? Я не вижу ничего, что могло бы помешать использованию __decorate в ES3.

Он использует дескриптор свойства, который недоступен в ES3.
Le 4 сен. 2015 14:03, "Колото" [email protected] автор:

@rbuckton https://github.com/rbuckton

При ориентации на ES3 нельзя использовать декораторы.

Почему? Я не вижу ничего, что могло бы помешать использованию __decorate в ES3.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/2249#issuecomment -137717517
.

@sccolbert Вы могли бы сделать это лучше с прокси ES6, а не с декораторами свойств.

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

Браузеры @DavidSouther пока не поддерживают прокси :(

@cybrown Да, он использует дескриптор свойства для методов и средств доступа. Я тестировал только простые свойства (поля без аксессуаров). Но кажется, что в ES3 можно разрешить декораторы для свойств (без аксессоров) и классов. Было бы полезно.

И поддельный дескриптор свойства может использоваться для методов при нацеливании на ES3. Что-то вроде { writable: true, enumerable: true, configurable: true } . Поэтому я не вижу причин не поддерживать ES3.

@sccolbert Понятно . В этом есть смысл. Между прочим, TypeScript работает над улучшенной _этой_ типизацией для функций и методов. Интересно, поможет ли это здесь? Хотя я полагаю, что проблема не в типизации, а в семантике времени выполнения.

@JsonFreeman Улучшенный _это_ набор текста интригует для некоторых других моих сценариев использования. У вас есть дополнительная информация по этому поводу?

Я думаю, что наиболее развернутая дискуссия об этой типизации находится на # 3694.

Ваше здоровье!

ошибка TS1207: декораторы нельзя применять к нескольким средствам доступа get / set с одним и тем же именем.

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

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

приведенный выше код не допускается, даже если он имеет больше смысла по сравнению с

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

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

Получатель и сеттер определяются одним вызовом Obect.defineProperty. Это скорее причуда js, объявление set и get, хотя и отдельные, на самом деле одно и то же объявление свойств. Проверка ошибок в компиляторе предназначена для предупреждения пользователей, когда они думают о них отдельно; декораторы применяются к дескриптору свойства только один раз.

просто интересно, так как компилятор может воспринимать get и set с тем же именем и объединяться в один Object.defineProperty, почему бы также не объединить декоратор?
или, возможно, флаг опции, чтобы сообщить компилятору объединить их и оставить предупреждающее сообщение вместо выдачи ошибки.

Спасибо

@TakoLittle : Причина, по которой мы этого не делаем сегодня, частично связана с составом декораторов. Декораторы следуют тем же принципам, что и композиция математических функций, где (_f_ ∘ _g _) (_ x_) составляется как _f _ (_ g _ (_ x_)). В том же смысле можно думать, что:

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

Примерно:

F(G(X))

Композиционность декораторов нарушается, когда вы украшаете как геттер, так и сеттер:

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

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

Как здесь складываются F и G ? Основан ли он исключительно на порядке документа (например, F(G(X)) )? Является ли каждый набор декораторов для геттера и установщика дискретным, а затем выполняется в порядке документа (например, G(F(X)) )? Подразумевают ли get и set какой-либо конкретный порядок (т.е. всегда ли get перед set или наоборот)? Пока мы не будем на 100% уверены, что это наиболее последовательный подход, который не удивит пользователей, или пока у нас не будет хорошо задокументированного подхода, который является частью предложения декораторов, по крайней мере, на стадии 2 или более высокого уровня принятия в рамках ECMA-262, мы считаем, что лучше всего быть более ограничительным и ошибочным, поскольку это позволяет нам ослабить это ограничение в будущем без внесения критических изменений, которые могут легко остаться незамеченными и, возможно, привести к неожиданному поведению во время выполнения.

@rbuckton большое спасибо за подробное объяснение
Команда TS отлично поработала !! ^^ г

Где для этого документация? и хотите связать реализацию коммита?

Спасибо.

@mhegazy Каков статус реализации последней версии спецификации. Я так понимаю, там есть некоторые изменения.

Эта проблема связана с исходной версией предложения. поскольку это завершено, мы закрываем этот вопрос. при любых обновлениях спецификации мы будем регистрировать новые проблемы и указывать все критические изменения. Я не думаю, что сейчас это предложение находится на стадии готовности, чтобы мы могли его реализовать. Мы тесно сотрудничаем с @wycats над новым предложением.

@omeid , вы можете найти документацию по адресу https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Decorators.md

@mhegazy Спасибо за обновление. Я хотел бы быть в курсе. Когда вы создаете новую проблему для обновления спецификации, пожалуйста, свяжите ее здесь, чтобы я мог получать уведомления и следить за ней. Сообщество Aurelia активно использует декораторы, и мы захотим синхронизировать как с TypeScript, так и с Babel в обновлении. Еще раз спасибо за отличную работу, которую делает команда TS!

Конечно, функциональное украшение необходимо.
Есть ли планы по декорированию других объектов в коде?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги