对于上下文,我正在编写一些代码,我想提取要从配置文件加载的模块列表。 模块类在启动时全部加载,然后使用加载的模块列表我们初始化请求的模块。
对于模块的注册表,我有:
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 的正确方法是什么?
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 代码链接