为什么类型 Record 有时会抱怨缺少键,有时则不会?

时间:2021-07-23 06:24:00

标签: typescript types record

考虑以下代码:

const testOne: Record<"foo"|"bar", string> = {
    "foo": "xyz"
};
const testTwo: Record<string, string> = {
    "foo": "xyz"
};

第一个示例会导致属性“bar”丢失的错误。第二个示例不会导致错误。这让我很困惑,因为我试图了解 Record 是否是一种类型,它暗示了其键类型的所有可能值的现有属性。

如果 Record 是一种要求所有可能的键实际存在于该类型的值中的类型,那么第一个示例不应导致错误。

如果 Record is 是一种类型,它要求所有可能的键实际存在于该类型的值中,那么第二个示例也应该导致错误。在这种情况下,不可能构造该类型的值,因为可能的键集是无限的。

如果有第三种选择——根据我尝试编译示例时实际发生的情况似乎有第三种选择——它是什么?我可以看到两种键类型之间的主要区别在于,一种具有有限的值集,另一种具有无限的值集。这是用来区分的吗?

除此之外,我能找到的唯一解释是 Record 不仅基于其键类型的值集进行区分,而且还基于其键类型的其他一些属性。如果是这样,密钥类型的哪些属性会有所不同?或者 Record 做的类型系统相当于“绕过接口,转换为实现类型并做一些你不应该做的事情”?

Record 的实现是

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

我可以在这里发现两件事。第一个是 K 与“keyof any”的绑定,但据我所知,这限制了 K 可以使用的类型,而不是对结果类型有效的值。其次,我们有一个正常的索引签名,所以我的猜测是我在 Record 中感到困惑的实际上是索引签名的行为——但是由于其他问题,我无法在没有 Record 的情况下轻松重现这种行为,所以我不不想仓促下结论。

1 个答案:

答案 0 :(得分:1)

让我们从实现开始:


type Record<K extends keyof any, T> = {
    [P in K]: T;
};

keyof any - 仅表示可用作任何对象键的所有允许类型。 现在,为此您可以使用 PropertyKey 内置类型。

{[P in K]: T;} 这只是一个常规的 for..in 循环。

因此,当您将联合类型 "foo"|"bar" 作为第一个参数传递给 Record 时,TS 编译器只会遍历每个联合并创建类似的东西:

type Result = {
    foo:string,
    bar: string,
}

这意味着您的最终对象应该具有最少的属性集:foobar

但是,当您仅将 string 作为第一个参数传递时,情况会有所不同。

type Result = Record<string, string>

type Result2 = {
    [P in string]: string
}

您可能已经注意到,ResultResult2 是相同的类型。

现在,您可能认为 Record<string, string> 等于索引接口:

interface Indexed {
    [prop: string]: string
}

type Result = Record<string, string>

type Check = Result extends Indexed ? true : false // true
type Check2 =  Indexed extends Result  ? true : false // true

但是这些类型的行为有点不同。 见this答案

更新

<块引用>

问题似乎仍然是 Record 是否要求所有可能的属性都实际存在。如果 Record 不要求所有可能的属性实际存在,为什么结果是具有必填字段而不是可选字段的对象类型?

请参阅Mapped Types docs

来自文档:

<块引用>

映射类型建立在索引签名的语法之上,用于声明未提前声明的属性类型:

因此,键入 Record<string, string> 意味着您不确切知道将用于记录的键,但您 100% 确定它将是 string。这是设计使然。

为什么 Partial<Record<string, string>>Record<string, string> 不同,因为 Partial 意味着值也可以是 undefined

换句话说,Partial<Record<string, string>> 等于 Record<string,string | undefined>

<块引用>

它是如何用于“inifite”类型的,例如字符串

这意味着如果key是string类型,你可以使用任何字符串来完成这个要求。没有任何无穷大的字符串集。