Typescript中嵌套对象的索引类型

时间:2019-01-05 03:04:43

标签: typescript

我正在寻找一个像这样的物体:

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中的Barmodules均为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模块中的构造函数,而不是模块中定义的类。

有人可以帮助我了解我在这里出了什么问题吗?

1 个答案:

答案 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()
};