我正在寻找一个像这样的物体:
const modules = {
Foo: {
dataSources: {
DataSourceA,
DataSourceB
}
},
Bar: {
dataSources: {
DataSourceC,
DataSourceD
}
}
};
将其变成这样:
const dataSources = {
DataSourceA: new DataSourceA(),
DataSourceB: new DataSourceB(),
DataSourceC: new DataSourceC(),
DataSourceD: new DataSourceD()
};
在维护每个数据源上的类型时。映射本身并不重要,它会从较大的模块对象中提取每个DataSource的类型,以创建一个表示每个DataSource实例的类型。
如果Foo
中的Bar
和modules
均为IModule
类型,则以下内容:
type Foo<T extends { [K in keyof T]: IModule }> = {
[K in keyof T]: new () => T[keyof T]["dataSources"]
};
import * as modules from "./modules";
const dataSources: Foo<typeof modules> = {
DataSourceA: new modules.Foo.dataSources.DataSourceA()
};
已关闭,但是出现以下错误:Type 'DataSourceA' provides no match for the signature 'new (): typeof import("src/modules/Foo/datasources/index")
使我相信我的类型正在尝试指向dataSources模块中的构造函数,而不是模块中定义的类。
有人可以帮助我了解我在这里出了什么问题吗?
答案 0 :(得分:1)
代码中有两个问题。第一个是从类中获取实例类型(因为我的理解是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()
};