如何在TypeScript

时间:2018-04-22 18:33:20

标签: typescript conditional-types

我正在寻找一种为以下对象创建TypeScript类型的方法,该对象具有两个已知密钥和一个具有已知类型的未知密钥:

interface ComboObject {
  known: boolean
  field: number
  [U: string]: string
}

const comboObject: ComboObject = {
  known: true
  field: 123
  unknownName: 'value'
}

该代码不起作用,因为TypeScript要求所有属性都与给定索引签名的类型匹配。但是,我不打算使用索引签名,我想键入一个我知道它的类型的字段,但我不知道它的名字。

我到目前为止唯一的解决方案是使用索引签名并设置所有可能类型的联合类型:

interface ComboObject {
  [U: string]: boolean | number | string
}

但是这有许多缺点,包括在已知字段上允许不正确的类型以及允许任意数量的未知密钥。

有更好的方法吗?使用TypeScript 2.8条件类型的东西有帮助吗?

2 个答案:

答案 0 :(得分:5)

你问过它。

让我们做一些类型操作来检测给定类型是否为union。它的工作方式是使用条件类型的distributive属性来扩展与成员的联合,然后注意每个成分比联合更窄。如果不是这样,那是因为联盟只有一个成分(所以它不是一个联盟):

type IsAUnion<T, Y=true, N=false> =
  [T] extends [infer U] ? U extends any ? [T] extends [U] ? N : Y : never : never

然后使用它来检测给定的string类型是否是单个字符串文字(所以:不是string,而不是never,而不是联合):

type IsASingleStringLiteral<T extends string, Y=true, N=false> =
  string extends T ? N : [T] extends [never] ? N : IsAUnion<T, N, Y>

现在我们可以开始了解您的具体问题。将BaseObject定义为您可以直接定义的ComboObject部分:

type BaseObject = { known: boolean, field: number };

在准备错误消息时,让我们定义一个ProperComboObject,这样当你陷入困境时,错误会提示你应该做些什么:

interface ProperComboObject extends BaseObject {
  '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!': string
}

这是主菜。 VerifyComboObject<C>采用C类型,如果符合您所需的ComboObject类型,则会保持不变;否则它会返回ProperComboObject(它也不符合)的错误。

type VerifyComboObject<C, X extends string = Exclude<keyof C, keyof BaseObject>> =
  C extends BaseObject & Record<X, string> ? 
  IsASingleStringLiteral<X, C, ProperComboObject> : ProperComboObject

通过将C解析为BaseObject和剩余的密钥X来工作。如果CBaseObject & Record<X, string>不匹配,那么您就失败了,因为这意味着它不是BaseObject,或者是具有额外非string属性的那个。然后,通过使用X检查IsASingleStringLiteral<X>,确保只有一个剩余密钥。

现在我们创建一个辅助函数,它要求输入参数匹配VerifyComboObject<C>,并返回输入不变。如果您只是想要一个正确类型的对象,它可以让您及早发现错误。或者您可以使用签名来帮助您自己的功能需要正确的类型:

const asComboObject = <C>(x: C & VerifyComboObject<C>): C => x;

让我们测试一下:

const okayComboObject = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value'
}); // okay

const wrongExtraKey = asComboObject({
  known: true,
  field: 123,
  unknownName: 3
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const missingExtraKey = asComboObject({
  known: true,
  field: 123
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const tooManyExtraKeys = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value',
  anAdditionalName: 'value'
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

第一个根据需要编译。最后三个因为与额外属性的数量和类型有关的不同原因而失败。错误信息有点神秘,但这是我能做的最好的事情。

您可以在the Playground中查看代码。

同样,我不认为我推荐用于生产代码。我喜欢玩类型系统,但这个特别感觉complicated and fragile,我不想对任何unforeseen consequences负责。

希望它对你有所帮助。祝你好运!

答案 1 :(得分:0)

一个不错的@jcalz

它给了我一些很好的见识,使我可以找到想要的地方。 我喜欢具有一些已知属性的BaseObject,并且BaseObject可以具有所需的任意多个BaseObject。

type BaseObject = { known: boolean, field: number };
type CoolType<C, X extends string | number | symbol = Exclude<keyof C, keyof BaseObject>> = BaseObject & Record<X, BaseObject>;
const asComboObject = <C>(x: C & CoolType<C>): C => x;

const tooManyExtraKeys = asComboObject({
     known: true,
     field: 123,
     unknownName: {
         known: false,
         field: 333
     },
     anAdditionalName: {
         known: true,
         field: 444
     },
});

这样,我就可以对已经拥有的结构进行类型检查,而无需进行太多更改。

ty