具有属性的类,其中属性本身包含类型为<T的扩展Class>

时间:2019-08-18 20:53:46

标签: typescript typescript-generics

我正在编写一个Engine类,该类在构造上获取Module类的映射,并实例化它们通过自身。然后将创建的实例存储在modules中。我使用泛型来确保引擎上存在的模块是已知的,并且可以由带有Engine类型的参数的函数声明为期望的模块。

Engine类还包含地图事件处理程序,该处理程序期望使用Engine实例作为其第一个参数的回调。可以通过公共方法注册处理程序。

一个模块可能看起来像这样:

class Renderer extends Module<{Renderer: Renderer}> {
    constructor(engine: Engine<{Renderer: Renderer}>) {
        super(engine);

        this.engine.addEventHandler('draw', drawSomething);
    }

    renderText(message: string) {
        console.log(message);
    }
}

...其中事件处理程序drawSomething如下所示:

function drawSomething(engine: Engine<{Renderer: Renderer}>): void {
    engine.modules.Renderer.renderText('hello world');
}

您可以找到解决了整个问题的here in TypeScript playground,并在第54行出现了错误。我还使用only the type definitions创建了一个问题,以查看我要使用的概念的形状。

我遇到的问题是,当我注册事件处理程序drawSomething时,在检查处理程序的类型是否正确时,似乎忽略了我传递的泛型。假设将调用处理程序而没有Renderer作为已知模块。打字稿告诉我:

  

类型'Engine <{}>'不能分配给类型'Engine <{Renderer:Renderer; }>'

如何确保TypeScript知道,当我将事件处理程序注册到具有某些模块的Engine实例时,该处理程序也将被传递调用这些模块的同一Engine实例吗?

我正在使用TypeScript 3.5.3。

2 个答案:

答案 0 :(得分:1)

关键问题是Module的递归类型定义,该定义接受另一个必须是其自身子类型的类型。

原始代码

class Module<TModulesOfEngine extends Modules = {}> {
    engine: Engine<TModulesOfEngine>;

    constructor(engine: Engine<TModulesOfEngine>) {
        this.engine = engine;
    }
}

class Engine<TModules extends Modules = {}> {
    public modules: TModules;
    public eventHandlers: {
        [eventName: string]: EventHandler<TModules>[];
    } = {};

    constructor(modules: ConstructableModules<TModules> = {} as ConstructableModules<TModules>) {
        this.modules = Object.keys(modules).reduce((allModules: TModules, moduleName: string): TModules => {
            const ModuleClass = modules[moduleName];
            return {
                ...allModules,
                [moduleName]: new ModuleClass(this),
            }
        }, {} as TModules);
    }
...

在某些情况下,可以使用polymorphic this type,如下例所示:Typescript - Generic type extending itself

但是,this类型不能在静态方法或构造函数中使用,这意味着以下错误。

class Module {
    engine: Engine<this>;

    // Error: A 'this' type is available only in a non-static member of a class or interface.
    constructor(engine: Engine<this>) { 
        this.engine = engine;
    }
}

尽管在美学上不令人满意,但是以下递归类型定义仍然有效。

解决方案

class Module<T extends Module<T>> {
    engine: Engine<T>;

    constructor(engine: Engine<T>) {
        this.engine = engine;
    }
}

class Engine<T extends Module<T>> {
    public modules: ModuleMap<T>;
    public eventHandlers: {
       [eventName: string]: EventHandler<T>[];
    } = {};

    constructor(modules: ConstructableModule<ModuleMap<T>>) {
        this.modules = Object.keys(modules).reduce((allModules: ModuleMap<T>, moduleName: string): ModuleMap<T> => {
            const ModuleClass = modules[moduleName];
            return {
                ...allModules,
                [moduleName]: new ModuleClass(this),
            }
        }, {} as ModuleMap<T>);
    }

    addEventHandler(name: string, handler: EventHandler<T>): void {
        this.eventHandlers = {
            ...this.eventHandlers,
            [name]: [
                ...(this.eventHandlers[name] || []),
                handler,
            ],
        };
    }
}

请参阅Typescript Playground上的工作示例。

答案 1 :(得分:0)

Modules接口的原始定义打破了泛型链。它的任何使用都会使TypeScript失去先前定义的Engine.modules的类型。由于它不接受传递给Module的任何泛型,因此无法捕获Engine上的预期模块:

class Module<TModulesOfEngine extends Modules = {}> {
    engine: Engine<TModulesOfEngine>;

    constructor(engine: Engine<TModulesOfEngine>) {
        this.engine = engine;
    }
}

// This is missing generic arguments, making it impossible to preserve typing.
interface Modules {
    [moduleName: string]: Module;
}

要解决此问题,Modules也需要开始接受TModulesOfEngine

class Module<TModulesOfEngine extends Modules<TModulesOfEngine> = {}> {
    engine: Engine<TModulesOfEngine>;

    constructor(engine: Engine<TModulesOfEngine>) {
        this.engine = engine;
    }
}

interface Modules<TModulesOfEngine extends Modules<TModulesOfEngine> = {}> {
    [moduleName: string]: Module<TModulesOfEngine>;
}

对此的完整实现可以在this TypeScript playground中找到。

我通过删除默认类型(例如TModules extends Modules = {})发现了默认值,该默认值揭示了在哪里遗忘了泛型的传递。