如何在 TypeScript 中指定属性是扩展另一个对象的类型?

问题描述 投票:0回答:1

对于上下文,我正在编写一些代码,我想提取要从配置文件加载的模块列表。 模块类在启动时全部加载,然后使用加载的模块列表我们初始化请求的模块。

对于模块的注册表,我有:

export abstract class Module {
    abstract initialize(config: any): void;
    abstract shutdown(): void;

    public static getModuleName(): string {
        throw new Error('Module must define a moduleName');
    }
}

随后的所有模块都通过脚本导入并与此类似地进行自我注册:

import {Module} from "../../framework/Module";
import {moduleRegistry} from "../../framework/ModuleRegistry";

class TwitchInitializer extends Module {
    public static MODULE_NAME: string = "twitch";

    public static getModuleName(): string {
        return TwitchInitializer.MODULE_NAME;
    }

    initialize(config: any): void {
    }

    shutdown(): void {
    }
}

moduleRegistry.register(TwitchInitializer);
import {Module} from "./Module";

class ModuleRegistry {
    modules: Record<string, typeof Module> = {};

    register<T extends typeof Module>(module: T): void {
        const moduleName = module.getModuleName();
        if (this.modules.hasOwnProperty(moduleName)) {
            throw `Module ${moduleName} already registered`;
        }
        this.modules[moduleName] = module;
    }

    get(moduleName: string): typeof Module {
        return this.modules[moduleName];
    }
}

const moduleRegistry = new ModuleRegistry();

export {moduleRegistry};

后来我有了

    const modules = [];

    // modulesToLoad is an array of strings, e.g. ['twitch']
    const modulesToLoad = configManager.get('MODULES').split(',');

modulesToLoad.forEach((moduleName) => {
    const moduleType = moduleRegistry.get(moduleName);
    if (moduleType === undefined) {
        logger.warn(`Could not load module of type ${moduleName}`);
        return;
    }
    modules.push(new moduleType);
})

这一切都很好,只是我的 IDE 向我展示了:

TS2511:无法创建抽象类的实例。

new moduleType

现在,虽然我知道它会运行 - 我更愿意确保我以正确的方式做事。 声明 modules 包含 Module 的非抽象扩展的 typeof 的正确方法是什么?

    

typescript
1个回答
0
投票

typeof Module

 构造签名
并且无法安全构造的 abstract
 
构建签名
;此外,构造签名需要接受零个参数(因为你写 new moduleType() 没有参数)。
从某种意义上说,您需要像 

(new () =>Module) & (typeof Module)

这样的类型,即具体构造函数的

交集
Module 的类型,这样它就具有您关心的所有静态属性,同时也是可构造的。
事实上,您可以使用该类型,一切都会正常:

type ConcreteModuleCtor = (new () => Module) & (typeof Module); class ModuleRegistry { modules: Record<string, ConcreteModuleCtor> = {}; register<T extends ConcreteModuleCtor>(module: T): void { const moduleName = module.getModuleName(); if (this.modules.hasOwnProperty(moduleName)) { throw `Module ${moduleName} already registered`; } this.modules[moduleName] = module; } get(moduleName: string): ConcreteModuleCtor { return this.modules[moduleName]!; } } const modules = []; declare const modulesToLoad: string[]; modulesToLoad.forEach((moduleName) => { const moduleType = moduleRegistry.get(moduleName); if (moduleType === undefined) { console.warn(`Could not load module of type ${moduleName}`); return; } modules.push(new moduleType); })

您的具体写作方式
ConcreteModuleCtor

并不是非常重要。

如果您想让您的要求非常明确和最小化,您可以将其定义为自己的接口,该接口仅提及您实际使用的 

Module

构造函数的各个方面:

interface ConcreteModuleCtor {
  getModuleName(): string;
  new(): Module;
}

它接受任何用零参数构造 
Module

子类型的东西,并且还有一个返回

getModuleName()
string
方法。它不需要具有
Module
的任何其他静态属性,因为它们没有被使用。
或者你可以通过

接口扩展

ConcreteModuleCtor
派生
typeof Module
,只要你先给typeof Module一个命名类型:
type AbstractModuleCtor = typeof Module;
interface ConcreteModuleCtor extends AbstractModuleCtor {
  new(): Module;
}

这些方法中的任何一种都可以发挥作用,但这不一定是所有方法的详尽列表。这些方法显然都不比其他方法“更正确”。可能存在一些用例或偏好,使一种方法或多或少合适或有吸引力,但这超出了所提出的问题的范围。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.