TypeScript 装饰抽象成员函数的能力

v1uwarro  于 6个月前  发布在  TypeScript
关注(0)|答案(9)|浏览(74)

搜索词

我在问题列表中搜索 "decorate abstract member",#20887 提到了这个问题,但已经关闭。

建议

能够装饰抽象函数及其参数。

用例

为了用 Typescript 实现一个类似 retrofit 的 http 请求库。从 retrofit 网站上的例子来看:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

当我们将其翻译成 Typescript 时,由于编译后接口会被擦除,我们需要装饰一个抽象类。但是,

@MyAPI() // Okay, abstract class is decoratable
abstract class GitHubService {
  @GET("users/{user}/repos")  // ERROR: Cannot decorate an abstract class member
  abstract Call<List<Repo>> listRepos(@Path("user") user: string);  // ERROR 
}

要解决这样的限制,我们不能使用抽象类,所以它变成了

@MyAPI() // Okay, abstract class is decoratable
class GitHubService {
  @GET("users/{user}/repos") 
  Call<List<Repo>> listRepos(@Path("user") user: user) {  
    throw new Error('unimplemented');
  } 
}

这显然不是优雅的做法。
我认为这样的功能可以在不破坏现有有效代码的情况下实现。
装饰器函数可以通过检查属性描述符的值是否为 undefined 来判断它是否正在装饰一个抽象成员。

function ClassMemberDecorator(prototype: {}, name: string, pd: PropertyDescriptor) {
  if (typeof pd.value === 'undefined') { // we're decorating abstract member
  }
}

function ClassMemberParamDecorator(prototype: {}, name: string, index: number) {
   if (typeof prototype[name] === 'undefiend') { // we're decorating abstract member param
   }
}

由于在目标 ES3 时,PropertyDescriptor.value 总是未定义的,因此这个特性仅在目标 ES5 及以上时才受支持。

检查清单

我的建议满足以下准则:

  • 这不会对现有的 TypeScript/JavaScript 代码造成破坏性更改
  • 这不会改变现有 JavaScript 代码的运行时行为
  • 这可以在不根据表达式的类型生成不同的 JS 的情况下实现
  • 这不是一个运行时特性(例如库功能、JavaScript 输出的非 ECMAScript 语法等)
  • 这个特性将与 TypeScript's Design Goals 的其他部分保持一致。
i5desfxk

i5desfxk1#

+1,非常希望拥有它。我的用例是为electron应用程序中的类生成IPC代理。

xbp102n0

xbp102n02#

我很想看到这个实现。
不仅仅是抽象类,接口也可以。它对于TypeScript编译器来说是一个方便的提示——自定义转换器。在处理接口时,无需将任何内容输出到最终的JavaScript代码中。
我在TypeScript runtime reflection上工作,它可以生成大量有用的数据,这些数据可以在运行时读取到元数据库中。你可以查找函数、接口、类及其构造函数、方法、参数、装饰器等。
如果能为接口添加注解,那将会非常有用。例如,我可以找到它们,找到它们的实现,并将指定在接口上的内容应用到实现中。

rsl1atfo

rsl1atfo3#

抽象类成员函数的装饰能力与接口不同:接口不在值空间中,编译后会被擦除。相反,抽象类在JS中只是转换为一个类,甚至可以示例化它。

到目前为止,TypeScript不允许这样做,所以我使用了这样的丑陋代码:

function Foo(target: any) {} // class decorator

function Bar(target: any, propertyKey?: string) {} // method decorator

@Foo
abstract class Test {
    @Bar
    existingMethod(): Date
        { throw '' } // dummy code (here is the ugly code)

}

const t : Test = new (Test as any)();
// just to show that instanciation of an abstract class is not a problem at runtime

然后,我向您展示如何生成JS代码(从TypeScript playground复制/粘贴):

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
function Foo(target) { }
function Bar(target, propertyKey) { }
let Test = class Test {
    existingMethod() { throw ''; }
};
__decorate([
    Bar,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Date)
], Test.prototype, "existingMethod", null);

// below, some code that would have been generated on an abstract method, if that were allowed
__decorate([
    Bar,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Date)
], Test.prototype, "nonExistingMethod", null);
// end of addon

Test = __decorate([
    Foo
], Test);
const t = new Test();

我插入了如果在Test类上设置抽象方法nonExistingMethod()时将生成的代码。我还运行了这段代码,有和没有Reflect.decorate(因为它的存在性在生成的代码中被检查)并且一切都很好。
使用那个抽象类和现代JavaScript(通常是使用Proxy进行拦截),在抽象成员上进行装饰是有意义的。请在编译器中删除对抽象方法上的错误:A decorator can only decorate a method implementation, not an overload.ts(1249)

6pp0gazn

6pp0gazn4#

你好,正如我在之前的评论中所提到的,似乎应该允许装饰抽象类的抽象成员函数?还缺少什么样的反馈?

v6ylcynt

v6ylcynt5#

@ppoulard,我们现在并不想改变装饰器语义(尤其是以一种允许之前不允许的事情的方式),因为有提案正在TC39委员会中通过以标准化它们。

v7pvogib

v7pvogib6#

真的希望看到错误被移除。现在正在使用装饰器来创建一个使用抽象类的模式,并提供伪代码。

qeeaahzv

qeeaahzv7#

这个特性是否可以在ECMA装饰器中使用?@RyanCavanaugh

ckocjqey

ckocjqey8#

看起来现在无法在抽象类成员上使用它,那么当前的实现应该是这样的:

@ClassDecorator
abstract class DummyAbstractClass {
    protected fooValue!: number;

    protected abstract get _foo(): number; // property
    protected abstract set _foo(value: number); // property
    protected abstract _bar(): void; // method

    @memberDecorator
    public get foo(): number {
        return this._foo;
    }

    // TS error: Decorators cannot be applied to multiple get/set accessors of the same name.
    // @memberDecorator is redundant here it applied to both of setter/getter
    public set foo(value: number) {
        this._foo = value;
    }

    @memberDecorator
    public bar(): void {
        return this._bar();
    }
}

class DummyClass extends DummyAbstractClass {
    protected get _foo(): number {
        return this.fooValue;
    }

    protected set _foo(value: number) {
        this.fooValue = value;
    }

    protected _bar(): void {
        console.log('_bar called');
    }
}

代码可以在TS Playground上找到

功能等待室>w<

iecba09b

iecba09b9#

这将是非常棒的!
但到目前为止,唯一的方法(不是最佳方法,因为它忽略了整个代码块中的错误)是拥有// @ts-expect-error// @ts-ignore:

abstract class Some {
  // @ts-expect-error
  @Decorator("value")  // no errors for whole block
  abstract method(param: string): void;
}

如果可能的话,在// @ts-expect-error// @ts-ignore处指定某些错误代码以跳过这些方法将是近乎完美的临时解决方案。问题:#38409,#19139等。也许类似于// @ts-expect-error("ts(1249)")

相关问题