Typescript: 装饰器

创建于 2015-03-07  ·  139评论  ·  资料来源: microsoft/TypeScript

ES7 提案

关于装饰器的 ES7 提案可以在这里找到: https :
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 。 在那里有 Function 的原因是什么?

我对 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 中添加了实现

更新:提案已更新,并添加了指向@wycats ES7 JavaScript 装饰器的链接。

令人难过的是,它变成了一个班级唯一的事情......
另外,他们是否超出了环境装饰器的范围?

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

此外,即使我的提案有很多问题,我也非常希望能够在函数上使用装饰器(即使我必须使用变量分配函数)并且不会提升。
这个 gist这样的案例对于函数和类来说似乎是一个非常好的装饰器用例(特别是与环境装饰器结合使用,如果它们最终仍然在范围内)

@fdecampredon这不适用于一般情况,因为装饰器本身就是表达式。 例如

myFunc();  // assumes function declaration is hoisted

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

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

如果你提升了装饰器的函数声明和应用,那么你就破坏了装饰器。 如果你只提升函数声明,而不是装饰器应用程序,你可以看到函数处于未装饰状态。 这里没有吸引人的解决方案。

这与 class 声明 extend 子句的问题相同,它在 ES6 中是一个表达式。 结果不是提升类声明,只是符号(类似于 var 声明,但不是函数声明)

Oups 没想到,谢谢@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() {
    // ...
}

函数表达式或 lambda 不会被提升。

是否可以在 typescript 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 的类型或实现不太正确,从类型来看,目标是一个函数,但在使用 typescript 时,它是原型对象。
我必须转换目标参数才能获得正确的类型:

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 对 express(但可以支持任何 Web 框架,定义了一个适配器接口)进行了包装: https :

我正在使用 ClassDecorator、ParameterDecorator 和 MethodDecorator。

@cybrown我刚刚在 #2635 中更新了ParameterDecorator的签名,现在在 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是的,即使在完全独立的功能(如内省)中,拥有运行时类型信息(如 AtScript 中所描述的 rtti)对于这种用法也会很棒。

@cmichaelgraham我们正在为 ES7 添加一个元数据 API的提案,该polyfill来读取元数据。

@rbuckton @cmichaelgraham最初设计了一个特殊的 TypeScript 装饰器,它会告诉编译器根据被装饰的目标将类型注入装饰器。 我认为它是@parameterTypes我认为正在删除该功能以支持更通用的元数据 api 是否正确? 如果是这样,我会支持。

你能谈谈关于 TypeScript 的现状吗? 是否计划发布 1.5? 如果是这样,编译器选项会是什么样子? 有用的一件事是让编译器仅为构造函数签名自动生成类型元数据。

@EisenbergEffect装饰器是 1.5 的一部分,您可以在我们最新的1.5-alpha版本中开始使用它。

至于元数据支持。 自@paramtypes的原始提案以来,设计已发生变化。 新设计正在使用 Reflect.metada 提案。 有关其工作原理的更多详细信息,请参阅 #2589。 @rbuckton也有一个 pollyfill 来使用这里的元数据: https :

@mhegazy我知道。 见这里: http :

我也熟悉元数据提案。 我一直在提供反馈。 我只是想知道是否删除了最初的@parameterTypes想法。

感谢@EisenbergEffect的分享。 :+1:

是的。 @paramtypes的唯一问题是它使发射由类型系统引导,并且需要全局程序信息。 这打破了单模块转译的场景(见#2499)。 另一种选择是将其放在调用站点上,这为装饰器用户而不是作者增加了大量工作。 所以我们回到绘图板并转而使用 Reflect.metadata 方法。

如果您还查看了提案的早期版本,出于同样的原因,我们不得不删除环境/设计时装饰器。

谢谢澄清。 这一切都说得通。 知道基于 Reflect 的方法是否会出现在 1.5 中吗?

是的。 它目前处于主人状态。 它应该在下一个版本中可用。 它目前是使用实验标志--emitDecoratorMetadata的选择加入功能; 它只向装饰实体添加元数据。

“它只向装饰实体添加元数据”你能扩展一下这个想法吗? 是否涉及特殊装饰器? 或者它是否将它添加到任何装饰器中? 换句话说,如果开发人员使用 Aurelia 的inject装饰器,那么是否会触发编译器生成元数据?

如果开发人员使用 Aurelia 的注入装饰器,那么会触发编译器生成元数据吗?

是的。

我的意思是:

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

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

使用--emitDecoratorMetadata编译器将为Foo而不是Bar发出类型元数据(即调用reflect.metadata('desing:paramtypes', [Number, String]) )。

这对我们有用。 我们将为下一个版本准备对 Reflect.metadata api 的支持,最有可能在下周发布。 感谢您的澄清!

那么为此发出了什么?

``` 语言=javascript
@注入
类 Foo {
构造函数(a:A,b:B){}
}

类酒吧{
构造函数(a:数字,b:B){}
}

would it be (for Foo):

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

@cmichaelgraham是的,大致就是这样生成的。

疯狂酷!

使用gulp-typescript来构建这个 repo - (好工作@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我们今天可以轻松启用此功能,而无需更改框架。 Aurelia 的 DI 库有一个钩子container.addParameterInfoLocator 。 您传递给它一个可以接受类型并返回其依赖项的函数。 对于我们的下一个版本(下周),我们可以将其添加到核心配置中,以便 TypeScript 开发人员轻松打开它。 我会在本周的发布中加入它,但我还没有意识到这已经改变了。

@EisenbergEffect 太棒了!! :+1:

我使用注释将对象的属性标记为可观察的,这将原语转换为可观察的对象(对于那些感兴趣的人,https://github.com/mweststrate/MOBservable/commit/8cc7fc0e20c000db660037c8b5c9d944fe4155d9)。

但是,特别是对于属性,感觉有点不自然,注解应用于原型,而不是在类本身的构造函数中,这意味着很难获得初始值,甚至this 。 解决方法是创建一个属性,并在其 getter/setter 中执行注释应该执行的实际逻辑,因为this和值然后可用。

除此之外,很棒的功能!

使用元数据反射 API,我能够进行简单的属性依赖注入。
您可以在此处查看所需的更改https://github.com/matjaz/property-DI/commit/2b4835e100b72d954b57d0e656ea524539ac17eb。

生成的代码中的 __metadata 装饰器不应该首先调用所有装饰器吗? 我想使用类的元数据创建简单的 deocrator,但是 __metadata('design:paramtypes', [TYPES....]) 被作为最后调用,所以我无法从 Reflect 获取这些数据

@ufon你能分享你正在看的代码吗?

当然,这里https://gist.github.com/ufon/5a2fa2481ac412117532

编辑:
我的不好,我的代码中还有一些其他错误,即使在类初始化后也无法获取类型

@ufon ,我不确定我当时是否看到了这个问题。 __metadata 是装饰器列表中的最后一项,这意味着它是第一个执行的,并且肯定是在注入执行之前。

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

其中__decorate定义使用 reduce 权限以相反的顺序执行装饰器。

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

这是为了匹配规范,装饰器(作为表达式)按照声明的顺序进行评估,但以相反的顺序执行,允许外部装饰器使用内部装饰器的结果。

我明白了,抱歉打扰:) 我的错,我的代码中还有其他一些错误,即使在类初始化后也无法获取类型

你如何查询他们? 你有 Reflect.metadata 的 pollyfill 吗?

添加到要点...
Reflect.getMetadataKeys(House);
这导致空数组..

是的,我需要“reflect-metadata”包

看起来polyfill失去了它,

想我明白了...
需要在打字稿的帮助程序代码之后移动

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

因此,及时 var __metadata 正在初始化,polyfill 尚未加载

但在生成代码之前我无法获得这些要求

编辑:更新了 gist .. 编译单个模块时,在解析 __metadata 之前无法加载 polyfill,因为 typescript 生成的代码总是移动到文件的顶部

哦..这是一个更大的问题。 记录问题 #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 和属性描述符)。

我的假设是@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()是不同的东西。 就像我期望CC()是不同的东西 - 第一个引用一个值,第二个调用一个值。 @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;
}

坦率地说,这是非常可怕的。 此外,我失去了对装饰器 F 的所有有价值的参数智能感知,因为现在它必须被概括。

有很多要说的装饰器是容易和一致的定义和记录。 让我们不要自欺欺人地认为每个使用我们代码的人都会与我们作为该代码的创建者具有相同的精明或理解力。

@Tharaxis我在装饰器设计的早期就考虑过这个问题,我理解你的具体观点。 正如@mhegazy@DavidSouther之前在此线程中提到的那样,这种行为与 JavaScript 中函数的标准行为一致。

您是正确的,任何尝试创建一个既可以充当装饰器又可以充当装饰器工厂的函数都可能有些复杂。 但是,一般而言,最好采用始终使用装饰器工厂的最佳实践,并为 linter 提供规则以确保采用这种实践。

另外,我刚刚提交了 PR #3249 来解决您之前关于装饰器类型检查不一致的观点。

@rbuckton

我是@Tharaxis 提到的那些不太精明的装饰器用户之一...

我想我会遇到的问题是装饰器是否应该与函数保持一致? 即在函数的情况下,返回 this.f 与 this.f() 对我来说完全有意义,因为有时我想要值,有时我想要产生值的东西。

在装饰器的情况下,在语言功能级别对我来说似乎并不那么明确。 我想争辩说我只想应用装饰器@F ,我真的不想知道它是作为工厂方法还是静态方法实现的。 除非我会失去一些装饰者本来拥有的能力。

如果上述内容被误导或无知,我深表歉意,我对 javascript 世界相对较新。

感谢和欢呼

我还必须问这个问题 - 保持与函数 _if_ 的句法奇偶校验(并且仅当)除了“因为它是使用函数定义的”之外没有真正的理由这样做是否有意义。 我认为装饰器是一个与函数完全无关的特性,只是_发生_要使用它们来定义。

现在@F@F()不相同的事实会导致以下问题:

  • 开发人员需要了解@F@F()之间的区别,以及为什么有时这可能有效,为什么有时可能无效(以及需要使用@F@F()可能完全是特定于框架/代码/用途的 - 因此从消费的角度来看不一致:想象一下declare function A(params?: any): ParameterDecoratordeclare 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这与我在@jonathandturner装饰器存储库中提出的问题相同: https :

@Tharaxis ,我同意你的第一点。 我不认为 ES7 提案应该阻止我们设计好的东西,无论如何我们的设计确实是 ES7 的先驱之一。

至于“装饰器不是函数的论点”,如果我没记错的话,我们不是在讨论装饰器本身,而是在讨论装饰器工厂。 不管装饰器和函数之间有什么区别,我认为装饰器 _factories_ 实际上只是函数。 听起来您要求编译器自动生成对装饰器工厂的调用,这是一个普通函数。 如果发生这种情况,程序员将无法区分装饰器工厂和装饰器。

此外,将装饰器视为函数的好处在于,消费者可以将其用作装饰器,也可以根据需要简单地调用它。

@JsonFreeman的论点围绕当前情况展开,其中有两种方法可以描述和调用装饰器,一种是通过工厂函数,另一种是作为“原始”函数(在工厂情况下是工厂调用的结果) ),而问题更多是关于“不应该只有一种方式,而让工厂采用这种方式”。

我要问的是是的,我们是否可以不只是让编译器将@F转换为等效的@F()调用,并且在使用未加括号的语法。 也许您可以详细说明“......程序员无法区分装饰器工厂和装饰器......”是什么意思,因为我认为这很容易区分。 装饰者总是来自工厂的回应,而工厂的名字就是装饰者的名字......没有那么难,还是我误解了?

至于你关于允许消费者只应用装饰器的最后一点,如果很好地描述了所有装饰器都使用工厂,那么自己调用装饰器很容易,与<decorator name>(target, name)相比,您只需执行<decorator name>(<argument list>)(target, name) <decorator name>(target, name)为例。 请记住,强制使用工厂意味着前一个示例将始终有效,而不强制使用它会导致后一个示例有时无法工作,并且完全取决于装饰器的实现方式 - 等待发生的头痛。

我觉得有必要指出,我对使用函数的装饰器没有问题,但是我的问题是用两种方法来描述同一件事会导致一致性问题。 特别是当这两种方式也意味着您的调用方法必须不同时。 这种差异阻碍了从重构到语言一致性的一切。

我在几篇帖子中描述的重构问题应该已经足以成为需要检查当前方法的一个原因。

大家好,按照@Tharaxis所说的,来自普通用户的最终价值仅 1.5 美分。

如果满足以下条件,我会对当前的提案感到非常满意:
a) 宇宙要求它。
b) 如果情况不同,装饰器将失去重要的功能。

后者当然是一种价值判断,其答案在某种程度上取决于您的开发人员类型(即普通用户/专家用户等)。 我属于前一类,通常分布在多种语言和框架中。 所以对我来说,“重要功能”不会包括以 2 种不同方式编写装饰器的灵活性。 如果存在所有权衡的例子,那就太好了。

理想情况下,如果@F和 @F() 无论装饰器是如何实现的都可以保持一致,那就太好了,但如果不是,我非常希望在编写装饰器时被限制使用工厂,而不必避免草丛中的耙子每当我使用它们时。

感谢和欢呼

似乎这个请求取决于装饰器工厂是使用装饰器的规范方式的想法,但我不明白这如何允许用户定义和使用原始装饰器。 如果我定义了一个装饰器F ,并且应用程序@F被视为@F() ,那么调用 F 的结果将用作装饰器,而不是 F 本身。 如果有人尝试应用原始装饰器而不是装饰器工厂,您是否建议我们在某处给出错误?

这个想法感觉就像它会颠倒装饰器和装饰器工厂的自然组合。 装饰器绝对是这里的原始构造,装饰器工厂是构建在装饰器之上的抽象层。 它们只是一种模式,仅此而已。 相反,如果装饰器工厂成为规范的东西,原始的,那么人们会定义一堆装饰器工厂,它们不接受任何参数并返回一个平面装饰器。 这会开始让人觉得很傻,它会颠倒什么被认为是基本的和什么被认为是更复杂的自然直觉。

我对装饰者非常警惕的一件事是过度的魔法。 当我不明白代码在做什么时,我个人会感到紧张,如果编译器偷偷添加了程序员没有编写的额外调用,那对我来说就像太多的巫术。

@JsonFreeman

正如我所提到的,我更喜欢丑陋的是装饰者的作者而不是用户。 但是,我同意许多没有 arg 的工厂非常丑陋。 可以使用装饰器来解决这个问题吗? 例如

// 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 的模式,或者一个返回 ClassDecorator、MethodDecorator、PropertyDecorator 的 0..n arg 工厂函数或参数装饰器。 但是,我会觉得该实现会导致其他错误(如果您有两个相互冲突的函数,哪个会被认为是最佳匹配呢?)并且只会在编译器中增加过度的复杂性。 简单地将@F调用转换为@F()将是更简单的解决方案,并且可以解决所述的各种问题。

抱歉,澄清一下,我并不是说您的解决方案很复杂。 我的意思是在应用它之前自动调用装饰器是不透明的。 插入的调用对用户是不可见的,我觉得很多用户都不会期望它。

我认为它目前的情况也不复杂。 正如您所指出的,它允许库作者对他们的功能是否打算用作装饰器含糊不清或一厢情愿。 我会答应你。 但是一个好的图书馆会让它变得清晰。

在您建议的方案中,您自动执行调用,当您使用任意装饰器表达式而不仅仅是标识符时会发生什么? 这也被调用了吗?

我同意,除非另有说明,否则隐式调用可能会令人惊讶,但仅限于那些没有使用过 C# 属性等概念的人。

您能否详细说明任意装饰器表达式的含义?

好肉汁,装饰器帮助定义装饰器,Typeception 的严重案例。

公平的电话。 适合我在周末下午晚些时候发表未经深思熟虑的评论。 学习到教训了。 互联网.撤消()。

我的观点是,如果事实证明一致的调用站点语法要求使用工厂,那么我对此非常满意。 毫无疑问,我会写一个装饰器来移除样板。 再次,我更喜欢在编写装饰器时有点痛苦,而不是使用它们重复痛苦(尽管此时可能会痛苦)。 其他人会不同意。

API增强不是也有问题吗? 没有工厂创建的装饰器不能在以后添加可选参数,因此我预测@F@F2()在我的未来。

我也没有看到在使用ff()时会出现这种情况,它们在消费方面是不同的用例。 在装饰器的情况下,我总是在当时和那里应用/调用目标上的装饰器,并且总是在幕后进行方法调用。

但对我来说,这个问题的关键是可用性问题,当我应用装饰器时,我不想通过谷歌来了解作者是如何实现它的——我对行为感兴趣,而不是包装。

干杯

最后总结一下,然后我会保持沉默。 简而言之,这对我来说真的只是一个交互设计问题。 我真的很想看到从外到内设计的装饰器,而不是相反。

作为 UI/UX 人员,我经常看到这种情况。 我曾与才华横溢的开发人员合作,他们提出了会伤害用户的 UI 解决方案(一个例子是在一个案例中使用两个保存按钮来解决事务复杂性 - 又是一个聪明人和好人)。 我只是认为当您深入了解复杂的实现细节逻辑时,很难忘记您所知道的并通过普通用户的眼睛看到它。

现在,如果作为平均值我完全错了,或者如果复杂性要求当前的设计那么一切都很好,我只需要动脑筋并学习。

干杯

如果您的装饰器不是标识符,而是其他一些表达式,您会自动调用它吗?例如,假设它是一个装饰器。 与回退函数表达式进行 OR 运算。 就像是:

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

然后你会自动调用它吗? 这会给您错误的结果,因为您最终会在应用默认装饰器之前调用它:

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

这似乎不对。

@JsonFreeman ,装饰器语法是否允许这样的任意表达式? 我不是 100% 肯定允许开发人员用那么多绳子来吊死自己一定是个好主意,但这只是我的想法(纯粹是因为我没有看到像解决重用/样板问题装饰者旨在解决的任意表达式而只是使代码看起来很丑陋,甚至更难以遵循)。

也就是说,我想它唯一可行的方法是,如果任意表达式本身评估为相同的工厂语法,那么:

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

我同意它确实导致您必须在任意装饰器周围放置更多繁琐的位,但我认为如果您使用任意表达式作为装饰器,则比添加() =>有更大的问题。

注意:我显然并不是说 lambda 语法是声明它的唯一方法,但是() =>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)

我知道支持此功能的机会很小。 但我想确认这不是一个类似的故事,比如structuralnominal打字。

当前的检查是您必须从装饰器中返回可分配给目标的内容。 缺少的部分是我们没有捕获类型的任何添加。 我认为我们不知道如何做到这一点,但我们也没有真正考虑过。 所以我认为这是一个公平的建议,但实施它的成本可能非常高。

理想情况下,我认为装饰器返回的类型应该与目标相同。 不知道您将在分配的源位置还是目标位置中使用装饰类型。

虽然我想我们真的应该合并装饰器返回类型和目标,有点像混合。

只是一些附加功能的想法。 由于 TypeScript 编译器输出的是 JavaScript,因此 Java 也有一些类似的语言,例如Xtend

您可以了解有关此项目的一些有趣想法。 寻找 Active Annotations “Active Annotations 允许开发人员通过库参与 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 中编写装饰器变得困难,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 谈判以收敛相同的语义? 我认为这是最重要的事情。

为什么使用装饰器来为特定实例指定一个值会很有用? 这不是属性初始值设定项的用途吗?

我的用例比示例更复杂。 我实际上是在使用装饰器来定义一个 getter,它返回一个绑定到属性的this上下文的对象,以便所述对象上的方法可以访问定义它的实例。

您可以将其视为在 Python 中模拟描述符协议,其中通过实例foo (即foo.bar )访问在类Foo上定义的方法bar调用返回BoundMethod的函数的__get__方法。 调用该对象时,将使用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 时不允许使用装饰器

为什么? 我看不到任何会阻止在 ES3 中使用__decorate的东西。

它使用 ES3 中没有的属性描述符
乐 9 月 4 日 2015 年下午 2:03,“Koloto”通知@github.com 一个 écrit:

@rbuckton https://github.com/rbuckton

面向 ES3 时不允许使用装饰器

为什么? 我看不到任何会阻止在 ES3 中使用 __decorate 的东西。


直接回复此邮件或在 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 正在改进函数和方法的 _this_ 类型。 我想知道这对这里是否有帮助。 虽然我认为打字对你来说不是问题,但它是运行时语义。

@JsonFreeman改进 _this_ 打字听起来对我的其他一些用例很有吸引力。 你有更多的信息吗?

我认为关于 _this_ 打字的最深入的讨论是在 #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){...}

Getter 和 setter 在对 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))

当你同时装饰 getter 和 setter 时,装饰器的组合性就会崩溃:

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

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

FG在这里如何组合? 它是否纯粹基于文档顺序(即F(G(X)) )? getter 和 setter 的每组装饰器是否都是离散的,然后按文档顺序执行(即G(F(X)) )? getset暗示任何特定的顺序(即get总是在set之前,反之亦然)? 直到我们 100% 确定最一致的方法不会让用户感到惊讶,或者有一个有据可查的方法作为装饰器提案的一部分,至少在 ECMA-262 中获得第 2 阶段或更好的接受,我们认为最好在这里更严格和错误,因为它允许我们在以后放宽该限制,而不会引入可能容易被忽视并可能在运行时导致意外行为的破坏性更改。

@rbuckton 非常感谢您的详细解释
TS 团队辛苦了!! ^^d

这方面的文档在哪里? 并注意链接实施提交?

谢谢。

@mhegazy最新版本规范的实施状态如何。 我知道那里有一些变化。

这个问题跟踪了提案的原始版本。 完成后,我们将关闭此问题。 对于规范的任何更新,我们将记录新问题并概述所有重大更改。 我认为该提案现在还没有准备好让我们继续执行。 我们正在与@wycats就新提案密切合作。

@mhegazy感谢您的更新。 我很乐意随时了解情况。 当您为规范更新创建新问题时,请在此处链接,以便我可以收到通知并关注。 Aurelia 社区大量使用装饰器,我们希望在更新时与 TypeScript 和 Babel 同步。 再次感谢 TS 团队所做的出色工作!

功能装饰当然是需要的。
是否还有计划在代码中装饰其他对象?

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

manekinekko picture manekinekko  ·  3评论

fwanicka picture fwanicka  ·  3评论

uber5001 picture uber5001  ·  3评论

jbondc picture jbondc  ·  3评论

kyasbal-1994 picture kyasbal-1994  ·  3评论