如何在 TypeScript 中键入具有已知和未知键的对象 [英] How do I type an object with known and unknown keys in TypeScript

查看:28
本文介绍了如何在 TypeScript 中键入具有已知和未知键的对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am looking for a way to create TypeScript types for the following object that has two known keys and one unknown key that has a known type:

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

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

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

That code does not work because TypeScript requires that all properties match the type of the given index signature. However, I am not looking to use index signatures, I want to type a single field where I know its type but I do not know its name.

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

The only solution I have so far is to use index signatures and set up a union type of all possible types:

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

但这有很多缺点,包括允许已知字段的类型不正确以及允许任意数量的未知键.

But that has many drawbacks including allowing incorrect types on the known fields as well as allowing an arbitrary number of unknown keys.

有更好的方法吗?TypeScript 2.8 条件类型有什么帮助吗?

Is there a better approach? Could something with TypeScript 2.8 conditional types help?

推荐答案

你要求的.

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

You asked for it.

Let's do some type manipulation to detect if a given type is a union or not. The way it works is to use the distributive property of conditional types to spread out a union to constituents, and then notice that each constituent is narrower than the union. If that isn't true, it's because the union has only one constituent (so it isn't a union):

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

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

Then use it to detect if a given string type is a single string literal (so: not string, not never, and not a union):

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

现在我们可以开始处理您的特定问题了.将 BaseObject 定义为 ComboObject 的一部分,您可以直接定义:

Now we can start taking about your particular issue. Define BaseObject as the part of ComboObject that you can define straightforwardly:

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

并为错误消息做准备,让我们定义一个 ProperComboObject 以便当您搞砸时,错误会提示您应该做什么:

And preparing for error messages, let's define a ProperComboObject so that when you mess up, the error gives some hint about what you were supposed to do:

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

主菜来了.VerifyComboObject<C> 接受一个类型 C 并且如果它符合您想要的 ComboObject 类型,则将它原封不动地返回;否则它会返回 ProperComboObject(它也不会符合)出现错误.

Here comes the main course. VerifyComboObject<C> takes a type C and returns it untouched if it conforms to your desired ComboObject type; otherwise it returns ProperComboObject (which it also won't conform to) for errors.

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

它的工作原理是将 C 分解为 BaseObject 和剩余的键 X.如果 C 不匹配 BaseObject &记录<X, string>,那么你失败了,因为这意味着它要么不是一个 BaseObject,要么是一个具有额外的非 string 属性的.然后,它通过检查 XIsASingleStringLiteral 来确保 正好有一个剩余的密钥.

It works by dissecting C into BaseObject and the remaining keys X. If C doesn't match BaseObject & Record<X, string>, then you've failed, since that means it's either not a BaseObject, or it is one with extra non-string properties. Then, it makes sure that there is exactly one remaining key, by checking X with IsASingleStringLiteral<X>.

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

Now we make a helper function which requires that the input parameter match VerifyComboObject<C>, and returns the input unchanged. It lets you catch mistakes early if you just want an object of the right type. Or you can use the signature to help make your own functions require the right type:

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 first one compiles, as desired. The last three fail for different reasons having to do with the number and type of extra properties. The error message is a little cryptic, but it's the best I can do.

可以看到活动的代码在操场.

You can see the code in action in the Playground.

同样,我不认为我建议将其用于生产代码.我喜欢使用类型系统,但这个系统感觉特别复杂和脆弱,我不会不想对任何不可预见的后果负责.

Again, I don't think I recommend that for production code. I love playing with the type system, but this one feels particularly complicated and fragile, and I wouldn't want to feel responsible for any unforeseen consequences.

希望对你有帮助.祝你好运!

Hope it helps you. Good luck!

这篇关于如何在 TypeScript 中键入具有已知和未知键的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆