【问题标题】:Difference between index signature and Record for empty object?空对象的索引签名和记录之间的区别?
【发布时间】:2019-06-03 15:08:55
【问题描述】:

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

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

两者之间是否有区别:

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

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

【问题讨论】:

  • 与我的理解没有太大区别 - Record&lt;S,T&gt; 接口是预定义的便利/助手
  • Record&lt;&gt; 是一个映射类型,所以你可以用它做更多有趣的事情,比如Record&lt;"foo" | "bar", string&gt;...见:stackoverflow.com/questions/51936369/…

标签: typescript mapped-types index-signature


【解决方案1】:

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&lt;string, string&gt;; 这样的类型时,内联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;
};

【讨论】:

    【解决方案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&lt;string, string&gt; 是完全一样的。

    【讨论】:

    • “你可以用 Record 做更多的事情,然后你可以用一个简单的索引签名”——你能举一个例子说明你可以用 Record 做的事情,你不能用索引签名做?谢谢!
    • @ChrisHaines Record&lt;"A" | "B", boolean&gt; 获取类型 { A: boolean, B: boolean }。或Record&lt;keyof T, boolean&gt; 获取与T 具有相同键但类型为boolean 的类型,就像示例一样
    • @ChrisHaines 不,它不能,索引签名参数必须是 stringnumber(甚至不是这两个作品的联合)。您可以使用映射的{ [key in "A" | "B"]: boolean },但这实际上只是写出Record&lt;"A" | "B", boolean&gt; 的真正含义。
    • @ChrisHaines 您将索引签名与映射类型混淆了。虽然它们具有相似的语法,但它们是不同的概念。
    • 另一个区别是索引语法适用于递归结构typescriptlang.org/play?#code/…而Record不适用typescriptlang.org/play?#code/…
    猜你喜欢
    • 2023-03-03
    • 2018-08-02
    • 1970-01-01
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 2014-04-07
    • 2011-03-14
    相关资源
    最近更新 更多