【问题标题】:Index type on nested object in TypescriptTypescript中嵌套对象的索引类型
【发布时间】:2019-01-07 03:54:29
【问题描述】:

我正在寻找一个看起来像这样的对象:

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 的类型以创建表示每个 DataSource 实例的类型。

如果modules 中的FooBar 都是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 模块中的构造函数,而不是模块中定义的类。

谁能帮我理解我哪里出错了?

【问题讨论】:

    标签: typescript


    【解决方案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()
    };
    

    【讨论】:

    • 非常感谢您抽出宝贵的时间写出如此详尽的答案,不仅解决了我的问题,而且还引导我完成了它,以便我可以一路学习。永远不要低估互联网上陌生人的善意:P.
    猜你喜欢
    • 1970-01-01
    • 2017-06-11
    • 2022-01-21
    • 2020-02-15
    • 1970-01-01
    • 2021-07-20
    • 1970-01-01
    • 2021-02-22
    • 2012-10-30
    相关资源
    最近更新 更多