本质上,modules
应该是heterogeneous array,其中每个元素不是某个固定的Module
类型,而是每个元素是不同的generic类型,如[GenericModule<Obj1>, GenericModule<Obj2>, ⋯]
,其中GenericModule<T>
是
interface GenericModule<T extends ObjBase> {
obj: T,
cls: Base<T>
}
而在你的forEach()
循环中,你真的只想处理GenericModule<T>
,some,T
,你并不关心.这是existentially quantified generic types的典型用例之一(例如"对于modules
、there exists的每个元素,都有T
个元素,使得该元素是GenericModule<T>
类型的).但是,像大多数其他带有泛型的语言一样,TypeScrip不直接支持存在泛型.(尽管microsoft/TypeScript#14466有一个长期开放的特性请求.)所以您需要一种不同的方法.
您可以采取不同的方法:
如果您想要支持一些固定的已知T
集合,您可以用union替换存在泛型.(存在主义泛型可以被认为是"无限联合",而常规泛型或"通用"泛型可以被认为是"无限intersections".)但在forEach()
内部,类型脚本将不能将其视为安全的.即使联合GenericModule<Obj1> | GenericModule<Obj2>
应该允许您安全地编写module.cls.processObj(module.obj)
,编译器仍抱怨.它看不到module.cls
和module.obj
之间的correlation.相反,module.cls.processObj
成为函数的联合,并且您只能安全地调用具有交集参数的函数(请参见the release notes for TS3.3),并且您将得到大约ObjA & ObjB
的错误.
相关unions 的这个问题是microsoft/TypeScript#30581次会议的主题.microsoft/TypeScript#47109中描述了推荐/支持的方法,它涉及使用"基本"映射接口将联合重构为特殊形式,在该接口中为联合的每个元素指定一个键,例如
interface Mapping {
a: ObjA,
b: ObjB
}
然后,您的所有操作都以该接口的形式编写,或者以该接口的通用indexes into编写,或者通过该接口写入mapped types.就像这样:
type Module<K extends keyof Mapping = keyof Mapping> =
{ [P in K]: GenericModule<Mapping[P]> }[K]
const modules: Module[] = [
{ obj: { id: 'a', someKey: '' } as ObjA, cls: new DerivedA() },
{ obj: { id: 'b', otherKey: '' } as ObjB, cls: new DerivedB() },
] as const;
modules.forEach(<K extends keyof Mapping>({ obj, cls }: Module<K>) => {
cls.processObj(obj); // okay
});
Module<K>
类型接受K
的泛型类型参数,它是keyof Mapping
的某个子集.Module<"a">
相当于GenericModule<ObjA>
,Module<"b">
相当于GenericModule<ObjB>
,Module
就相当于default Module<"a" | "b">
,相当于和GenericModule<ObjA> | GenericModule<B>
.所以modules
是Module[]
类型的.
现在forEach()
回调是通用的,接受Module<K>
. 这是可行的,因为类型cls.process
被视为单一的泛型函数类型(obj: Mapping[K]) => string
,而obj
被视为Mapping[K]
.不再涉及联合,只是使用预期的参数类型调用单个泛型函数类型.
另一种方法,如果你没有一个固定的类型列表来支持,或者你不想给这些类型分配键,那就是通过将它们转换为类似Promise
的 struct 来模拟存在泛型. 使用存在泛型相当于提供通用泛型,反之亦然.这在microsoft/TypeScript#14466中的this comment处有描述.
给定GenericModule<T>
,我们可以定义Module
,即存在主义版本,如下所示:
type Module = <R>(cb: <T extends ObjBase>(module: GenericModule<T>) => R) => R;
因此,Module
就像是GenericModule<T>
的promise ,而你不知道T
.您所能做的就是给它一个类似then
的回调,该回调返回您do知道其类型的内容.对于任何普通的GenericModule
,你可以把它包装成一个Module
:
const someModule = <T extends ObjBase>(module: GenericModule<T>): Module => cb => cb(module);
然后,您的modules
数组可以重新包装:
const someModules: Module[] =
[someModule(modules[0]), someModule(modules[1])];
现在你可以通过给它们每个回调来处理它们:
someModules.forEach(m => m(({ obj, cls }) => {
cls.processObj(obj); // okay
}));
这些方法中的每一种都保留了一定程度的类型安全,而且每种方法都有优点和缺点,而且每一种方法都不是没有一些努力和复杂性.对于特定的任务,哪一个是可接受的,如果有的话,取决于用例和需求.
Playground link to code个