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

时间:2019-01-08 21:31:58

标签: typescript

我对TypeScript还是很陌生,我无法弄清楚索引签名和记录类型之间的区别。有人可以解释这些差异以及何时使用一种与另一种吗?

具体来说,我正在寻找一种对象的类型,该对象的键和值将具有随机字符串,并将对其进行迭代。

之间有区别吗

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

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

2 个答案:

答案 0 :(得分:3)

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;
};

答案 1 :(得分:2)

使用Record代替简单的索引签名是否是一个好主意,这可能是一个有争议的问题(正如David Shereet在回答中指出的那样)。同样值得一提的是,Record比简单的索引签名可以做更多的事情。

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

虽然很难找到类型的详尽列表,但此answer中的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>完全是同一回事。