空对象的索引签名和记录之间的区别?

Difference between index signature and Record for empty object?

我无法弄清楚索引签名和记录类型之间的区别。有人可以解释其中的差异以及何时使用它们吗?

具体来说,我希望定义一个对象的类型,该对象的键和值将具有随机字符串,这些字符串将被迭代。

有区别吗:

let objectVariable: Record<string, string> = {}

let objectVariable2: {[index: string]: string} = {}

Record 的定义是:

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

创建类似 type MyType = Record<string, string>; 的类型时,内联 Record 会导致以下类型:

type MyType = {
    [P in string]: string;
};

这是说要在集合 string 中创建一个具有字符串 属性 名称的对象类型。由于 string 是无限的,因此字符串的可能性是无限的(不像 "prop1" | "prop2" 这样的字符串文字类型的联合)...限制是属性必须具有 string.

类型

所以是的,从类型检查的角度来看它基本上等同于没有映射类型的索引签名示例 ({ [index: string]: string; }.

使用普通索引签名

虽然以这种方式使用 Record 有点奇怪,但很多人可能不明白发生了什么。当可以有任意数量的属性时,一种更常见的表达意图的方法是不使用映射类型:

type ObjectWithStringProperties = {
    [index: string]: string;
};

这有一个额外的好处,可以帮助解释密钥应该是什么。例如:

type PersonsByName = {
    [name: string]: Person;
};
const collection: PersonsByName = {};

请注意,以这种方式,类型是不同的,因为使用具有这种类型的对象的开发人员将在他们的编辑器中查看额外描述的键名信息。

使用Record

注意Record通常像下面这样使用:

type ThreeStringProps = Record<"prop1" | "prop2" | "prop3", string>;
// goes to...
type ThreeStringProps = { [P in "prop1" | "prop2" | "prop3"]: string; };
// goes to...
type ThreeStringProps = {
    prop1: string;
    prop2: string;
    prop3: string;
};

使用 Record 而不是普通索引签名是否是一个好主意可能是一个有争议的问题(正如 David Shereet 在他的回答中指出的那样)。此外,您可以使用 Record 做更多的事情,然后您可以使用简单的索引签名,这一事实也应该被提及。

这个问题的主要部分(在我的阅读中)是这两种类型是否相同。它们显然以不同的方式声明,但它们是 相同的 类型。虽然它们显然是兼容的(也就是说,您可以将一个分配给另一个,反之亦然),但问题是是否存在无法做到这一点的极端情况。

虽然很难找到一个详尽的类型列表,但 Matt McCutchen 在这个 中提供了一个有趣的类型,可以检测是否存在 readonly 修饰符(这简单兼容性不检测之间的差异)。我推测如果 Record 和索引签名在 Matt 那里使用它们的方式被认为是相同的(作为泛型函数签名的一部分),它们几乎是相同的类型,以不同的方式声明:

type IfEquals<X, Y> =
    (<T>() => T extends X ? 1 : 2) extends
    (<T>() => T extends Y ? 1 : 2) ? "Y" : "N";

let same : IfEquals<{x: string}, {x: string}>= "Y"
let notsame : IfEquals<{ y: string }, { x: string }>= "N"
let notsamero: IfEquals<{ readonly x: string }, { x: string }> = "N"
let samerecord: IfEquals<{ [x: string]:string }, Record<string, string>> = "Y"

正如我们在上一个示例中看到的,samerecord 的类型是 Y,这意味着编译器将这两种类型视为同一事物。因此我推测 { [x: string]:string }Record<string, string> 是完全一样的东西。