【问题标题】:Infer Generic type in dictionary property推断字典属性中的通用类型
【发布时间】:2019-09-28 11:31:57
【问题描述】:

我有一个通用接口,我希望其中一个属性只包含来自另一个属性的键,并且具有相同的类型......

interface IMyInterface<T> {
    propA: T;
    propB: { [key in keyof T]?: T[key] };
}

这很好,我以前做过很多次类似的事情......基本上我希望它根据我传递给 propA 的内容来推断 T 是什么。

但这是我的问题,我有一本包含许多 IMyInterface 类型的字典....

interface IMyDictionary {
    [key: string]: IMyInterface;
}

这就是我的问题所在,上面的定义需要一个泛型类型......但是我使用的任何类型都将应用于每个属性。如果我使用任何比它失败的点,因为在声明我的字典时它假定一切都是任何的。

interface IInterfaceA {
    name: string;
    age: number;
}

const objA: IInterfaceA = {
    name: 'John',
    age: 18
}

const myDictionary: IMyDictionary {
    test1: {
        propA: objA,
        propB: { age: 40 }
    }
}

所以基本上,当将值分配给上面的 myDictionary 时,我希望它推断 propA 的类型为 IInterfaceA,并且在 propB 中只允许属性名称为字符串类型,年龄为数字类型。

我可以通过不定义类型 IMyDictionary 并一项一项地创建每个项目,然后将它们分配给一个新对象 (myDictionary) 来轻松做到这一点....

const test1: IMyInterface = {
    propA: objA,
    propB: { age: 40 }
}

const myDictionary = { test1 }

这将推断 test1 的泛型类型,然后推断 myDictionary 的类型,使用确切的属性名称,而不是字符串数组。但如果可能的话,我宁愿使用普通的旧对象赋值语法。

我想不出一种方法来声明 IMyDictionary 的类型以强制它推断 IMyInterface 的每个实例的泛型类型。或者,我对定义 IMyInterface 以推断 propB 的属性和类型的不同方法持开放态度。

提前致谢!

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您要查找的类型是字典,其中每个条目是some IMyInterface&lt;T&gt; 类型,但不是任何特定 T。这可能最好表示为existential type,例如(可能){[k: string]: IMyInterface&lt;exists T&gt;},目前 TypeScript 本身不支持(大多数其他具有泛型的语言也不支持)。 TypeScript(以及大多数其他具有泛型的语言)只有 universal 类型:想要X&lt;T&gt; 类型的值的人可以为T 指定他们想要的任何类型,并且值的提供者必须能够遵守。存在类型则相反:想要提供X&lt;exists T&gt; 这样类型的值的人可以为T 选择他们想要的任何特定类型,接收者 该值必须遵守。存在类型允许您将泛型类型“隐藏”在非泛型类型中。

    但是,TypeScript 没有存在类型,所以我们必须做点别的。 (嗯,它没有 native 存在类型。您可以 emulate them 使用泛型函数并通过回调反转控制,但这比我要建议的解决方案使用起来更复杂下一个。如果您仍然对存在主义感兴趣,可以阅读有关它的链接文章)


    我们可以做的下一个最好的事情是将IMyDictionary 的形状描述为一个泛型类型,我们并不真正关心它,并让编译器尽可能地为我们推断它。这是一种方法:

    type IMyDictionary<T> = { [K in keyof T]: IMyInterface<T[K]> }
    const asIMyDictionary = <T>(d: IMyDictionary<T>) => d;
    

    这个想法是,不是将变量声明为IMyDictionary 类型,而是使用辅助函数asIMyDictionary() 获取值并生成合适的类型IMyDictionary&lt;T&gt;。顺便说一句,这只适用,因为IMyDictionary&lt;T&gt; 类型是homomorphic mapped type,所以T 可以从IMyDictionary&lt;T&gt; 类型的值中推断出来。让我们看看它的实际效果:

    const myDictionary = asIMyDictionary({
      test1: {
        propA: { name: "John", age: 18 },
        propB: { age: 40 }
      },
      test2: {
        propA: { size: 10, color: "blue" },
        propB: { color: "purple" }
      },
      test3: {
        propA: { problem: true },
        propB: { oops: false } // error!
        //       ~~~~~~~~~~~~
        // 'oops' does not exist in type '{ problem?: boolean | undefined; }'
      }
    });
    

    这很有效,并且会在您期望的地方出现错误。耶!

    这里需要注意的是,您仍然需要在您不关心的 T 类型周围拖拉。您希望只处理具体的IMyDictionary 的任何函数或类型都必须成为通用的。也许这对你来说不是太痛苦。如果是,您可以考虑“模拟”存在类型。但是我已经在这里写了很多,所以希望以上内容对您有所帮助……如果您需要我写出模拟的存在版本,我可以。

    祝你好运!

    【讨论】:

    • 这看起来是一个很好的解决方案。我不介意在我的定义中加入一些额外的泛型,只要我的任务对其他开发人员来说是无痛的。我会在星期一试试这个,但它似乎相当防弹。谢谢!
    猜你喜欢
    • 2020-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-05
    • 1970-01-01
    • 2019-08-31
    • 1970-01-01
    相关资源
    最近更新 更多