如何从 TypeScript 中的模板扩展抽象 class?

How to extends abstract class from template in TypeScript?

我有一个类似的架构,我想用一个模板扩展我的 TemplateService,但实际上我有一个错误。有没有办法从模板扩展抽象 class?

// Error message
ReferenceError: Service is not defined
abstract class TemplateService<Service> extends Service
                                               ^
// My code
abstract class TemplateService<Service> extends Service { // Cannot find name 'Service'.
    constructor() {
        const name = "Hello world !";

        super(name);
    }

    public getJob(): string {
        return "Job";
    }

    protected getAge(): number {
        return 25;
    }
}

class ImportedService {
    constructor(private _name: string) {}

    public getName() {
        return this._name;
    }
}

class MyService extends TemplateService<ImportedService> {
    constructor() {
        super();
    }

    public fooMethod(): void {
        ...
    }
}

写的是类别错误

abstract class TemplateService<Service> extends Service {}

TypeScript 的静态类型系统,包括实际运行的发出的 JavaScript 中的 generic type parameters, is erased。在上面,abstract<Service> 是静态类型系统的一部分,而其余的 JavaScript 是有效的(至少对于 JavaScript 的现代版本)。您编写的代码将发出类似

的内容
class TemplateService extends Service {}

希望能说明问题所在。除非范围内碰巧有一个名为 Service 的 class 构造函数,否则这将是一个运行时错误。你不能 subclass a type;你需要子class一个构造函数值.


TypeScript 通过 mixins 的概念对 subclass 任意构造函数值提供了一些支持。不是将 TemplateService 写成 class,而是将其设为接受 class 构造函数作为输入的 class 工厂函数 。从概念上讲,它看起来像

// don't write this, it's not valid TS
function TemplateService<C extends new (name: string) => object>(ctor: C) {
    return class extends ctor {  // error!
// ------> ~~~~~
        constructor() {
            const name = "Hello world !";
            super(name);
        }
        public getJob(): string {
            return "Job";
        }

        protected getAge(): number {
            return 25;
        }
    };
}

你会像这样使用它

class ImportedService {
    constructor(private _name: string) { }

    public getName() {
        return this._name;
    }
}

class MyService extends TemplateService(ImportedService) {
    constructor() {
        super();
    }

    public fooMethod(): void {
        console.log(this.getName());
        console.log(this.getJob());
        console.log(this.getAge())
    }
}

不幸的是,TypeScript 的 mixin 支持假定您将只传递构造函数参数,因此上面的 TemplateService 实现会产生编译器错误。关于这个有一些未解决的问题,例如 microsoft/TypeScript#37142. For now all I could say is to work around it by using several type assertions 欺骗编译器允许 mixin 并给它正确的类型。

对于此处的特定示例代码,它可能如下所示:

function TemplateService<C extends new (name: string) => object>(ctor: C) {
    const c = class extends (ctor as new (...args: any) => object) {
        constructor() {
            const name = "Hello world !";
            super(name);
        }
        public getJob(): string {
            return "Job";
        }

        protected getAge(): number {
            return 25;
        }
    };

    return c as {
        new(): InstanceType<C> & InstanceType<typeof c>;
    }
}

现在编译器认为 TemplateService(ImportedService) 是一个 no-arg 构造函数,它生成 ImportedService 的组合(通过 intersection)和匿名 class 表达式与 getJob() 方法和 protected getAge() 方法。

Playground link to code