代码中有两个问题。第一个是从类中获取实例类型(因为我的理解是 DataSource* 是类)。为此,我们可以使用内置条件类型InstanceType。比如:
const ds = DataSourceA; // typed as typeof DataSourceA
type dsInstance = InstanceType<typeof ds> // is DataSourceA
第二部分是扁平化模块结构。我们需要做的第一件事是应用映射类型来获取所有数据源实例类型:
type IDataSources = {[name: string]: new (...a: any[]) => any }
type DataSourceInstances<T extends IDataSources> = {
[P in keyof T] : InstanceType<T[P]>
}
//The type below is the same as { DataSourceC: DataSourceC; DataSourceD: DataSourceD; }
type dsModules = DataSourceInstances<typeof modules['Bar']['dataSources']>
所以现在我们可以获取模块中所有数据源的实例。如果我们使用keyof typeof modules而不是特定的模块名称,我们也可以通过类似的方式获得所有模块中所有数据源的联合:
//The type below is the same as {DataSourceA: DataSourceA;DataSourceB: DataSourceB;} | {DataSourceC: DataSourceC;DataSourceD: DataSourceD;}
type dsModules = DataSourceInstances<typeof modules[keyof typeof modules]['dataSources']>
但我们显然不想要联合,我们希望将所有这些数据源放在一个对象中。如果我们可以将联合转换为交叉点,我们基本上就在那里。我们可以在 jcalz 的 UnionToIntesection 的帮助下做到这一点(支持他的回答 here)
type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type AllDataSources<T extends { [K in keyof T]: IModule }, U = DataSourceInstances<T[keyof T]['dataSources']>> = Id<UnionToIntersection<U>>
//below is same as { DataSourceA: DataSourceA; DataSourceB: DataSourceB } & { DataSourceC: DataSourceC; DataSourceD: DataSourceD;}
type moduleDs = AllDataSources<typeof modules>
现在这将按预期工作,但如果您将鼠标悬停在 moduleDs 上,您会看到一个非常丑陋且令人困惑的类型:
DataSourceInstances<{
DataSourceA: typeof DataSourceA;
DataSourceB: typeof DataSourceB;
}> & DataSourceInstances<{
DataSourceC: typeof DataSourceC;
DataSourceD: typeof DataSourceD;
}>
如果您想将其展平以获得更好的工具提示(仅出于这个原因),您可以使用 Nurbol Alpysbayev 描述的技巧here(我再次鼓励您支持他的答案:))
总而言之,我们得到:
type IModule = { dataSources: IDataSources }
type IDataSources = {[name: string]: new (...a: any[]) => any }
type DataSourceInstances<T extends IDataSources> = {
[P in keyof T] : InstanceType<T[P]>
}
type Id<T> = {} & { [P in keyof T]: T[P] }
type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type AllDataSources<T extends { [K in keyof T]: IModule }, U = DataSourceInstances<T[keyof T]['dataSources']>> = Id<UnionToIntersection<U>>
//tooltip will be { DataSourceA: DataSourceA; DataSourceB: DataSourceB; DataSourceC: DataSourceC; DataSourceD: DataSourceD;}
const dataSources: AllDataSources<typeof modules> = {
DataSourceA: new DataSourceA(),
DataSourceB: new DataSourceB(),
DataSourceC: new DataSourceC(),
DataSourceD: new DataSourceD()
};