为什么 type Record 有时会抱怨缺少键,有时却不会?

Why does type Record sometimes complain about missing keys and sometimes not?

考虑以下代码:

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

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

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

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

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

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

Record 的实现是

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

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

让我们从实施开始:


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

但是这些类型的行为有点不同。 请参阅 答案

更新

The question still seems to be whether Record demands all possible properties to actually exist. If Record does not demand all possible properties to actually exist, why is the result an object type with required fields instead of optional fields?

请参阅Mapped Types docs

来自文档:

Mapped types build on the syntax for index signatures, which are used to declare the types of properties which has not been declared ahead of time:

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

为什么Partial<Record<string, string>>Record<string, string>不一样,因为Partial表示值也可以是undefined

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

how does it work for "inifite" types such as string at all

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