打字稿中的记录类型是什么?


182

Record<K, T>Typescript是什么意思?

Typescript 2.1引入了该Record类型,并在一个示例中进行了描述:

// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

打字稿2.1

高级类型页提到Record的映射类型旁边标题下ReadonlyPartialPick,这似乎是它的定义:

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

只读,部分和选择是同态的,而记录不是同态的。记录不是同态的一个线索是它不需要输入类型来复制以下属性:

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

就是这样。除了以上引用,typescriptlang.orgRecord上没有其他提及。

问题

  1. 有人可以给一个简单的定义Record吗?

  2. Record<K,T>仅仅说“此对象上的所有属性都会有型的方式T”?可能不是所有属性,因为K都有某些用途...

  3. K泛型是否在对象上禁止了不是的其他键K,或者是否允许它们并且仅指示其属性未转换为T

  4. 对于给定的示例:

     type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

与此完全一样吗?:

    type ThreeStringProps = {prop1: string, prop2: string, prop3: string}

6
4.的答案几乎是“是”,因此应该可以回答您的其他问题。
jcalz

Answers:


210
  1. 有人可以给一个简单的定义Record吗?

A Record<K, T>是一种对象类型,其属性键为K,属性值为T。也就是说,keyof Record<K, T>等效于K,并且Record<K, T>[K](基本)等效于T

  1. Record<K,T>仅仅说“此对象上的所有属性都会有型的方式T”?可能不是所有对象,因为K都有某些用途...

如您所述,K有一个目的...将属性键限制为特定值。如果您想接受所有可能的字符串值键,则可以执行类似的操作Record<string, T>,但是惯用的方式是使用索引签名,例如{ [k: string]: T }

  1. K泛型是否在对象上禁止了不是的其他键K,或者是否允许它们并且仅指示其属性未转换为T

它并不完全“禁止”其他键:毕竟,通常允许一个值具有其类型中未明确提及的属性……但它无法识别出存在此类属性:

declare const x: Record<"a", string>;
x.b; // error, Property 'b' does not exist on type 'Record<"a", string>'

并将它们视为有时会被拒绝的多余属性

declare function acceptR(x: Record<"a", string>): void;
acceptR({a: "hey", b: "you"}); // error, Object literal may only specify known properties

有时被接受:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. 对于给定的示例:

    type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

    与此完全一样吗?:

    type ThreeStringProps = {prop1: string, prop2: string, prop3: string}

是!

希望有帮助。祝好运!


1
博学多才,又是一个问题,为什么“惯用的方式是使用索引签名”而不是“记录”?我找不到有关这种“惯用方式”的任何相关信息。
legend80s 19-10-28

2
如果需要,可以Record<string, V>用来表示{[x: string]: V};我什至可能自己做了。索引签名版本更直接:它们是相同的类型,但是前者是映射类型的类型别名,其结果为索引签名,而后者只是直接的索引签名。在其他条件相同的情况下,我建议后者。同样Record<"a", string>{a: string}除非有其他令人信服的上下文原因,否则我不会用它代替。
jcalz

1
其他条件都一样,我建议后者。 ”为什么会这样?我的Type-type脚本前人对此表示同意,但是我知道,对于来自C#端的人来说,前者会更多,更自我评论,而JavaScript-to-Typescripter不会更糟。您是否只想跳过这些结构的翻译步骤?
鲁芬

1
我的看法是:Record<string, V>仅当您已经知道索引签名在TypeScript中如何工作时,行为才有意义。例如,显然x: Record<string, string>x.foostring在编译时使用a ,但实际上可能是string | undefined。这是工作方式上的差距--strictNullChecks(请参阅#13778)。我宁愿有新人处理{[x: string]: V},而不是直接期待他们从跟随链Record<string, V>通过{[P in string]: V}对指数的签名行为。
jcalz

我想指出的是,逻辑上可以将类型定义为该类型中包含的所有值的集合。考虑到这种解释,我认为Record <string,V>作为简化代码而不是布置所有可能值的抽象是合理的。它类似于实用程序类型文档中的示例:Record <T,V>,其中类型T ='a'| 'b'| 'C'。从不执行Record <'a',string>并不是一个很好的反例,因为它没有遵循相同的模式。它也不会像其他示例那样通过抽象来增加重用或简化代码。
Scott Leonard

68

记录使您可以从联合创建新类型。联合中的值用作新类型的属性。

例如,假设我有一个这样的联合体:

type CatNames = "miffy" | "boris" | "mordred";

现在,我想创建一个包含有关所有猫的信息的对象,我可以使用CatName Union中的值作为键来创建新类型。

type CatList = Record<CatNames, {age: number}>

如果要满足此CatList,必须创建一个这样的对象:

const cats:CatList = {
  miffy: { age:99 },
  boris: { age:16 },
  mordred: { age:600 }
}

您将获得非常强大的类型安全性:

  • 如果我忘记了猫,就会报错。
  • 如果我添加了不允许的猫,则会出现错误。
  • 如果以后更改CatNames,则会收到错误消息。这特别有用,因为CatNames可能是从另一个文件导入的,并且可能在许多地方使用。

实际的React示例。

我最近用它来创建一个Status组件。该组件将收到状态道具,然后呈现一个图标。为了说明起见,我在这里简化了很多代码

我有一个这样的工会:

type Statuses = "failed" | "complete";

我用它来创建这样的对象:

const icons: Record<
  Statuses,
  { iconType: IconTypes; iconColor: IconColors }
> = {
  failed: {
    iconType: "warning",
    iconColor: "red"
  },
  complete: {
    iconType: "check",
    iconColor: "green"
  };

然后可以通过将对象中的元素分解为prop来进行渲染,如下所示:

const Status = ({status}) => <Icon {...icons[status]} />

如果稍后扩展或更改Status联合,则我知道Status组件将无法编译,并且会收到一个错误,可以立即修复。这使我可以向应用程序添加其他错误状态。

请注意,实际的应用程序具有在多个位置引用的数十个错误状态,因此这种类型的安全性非常有用。


我认为大部分时间都type Statuses生活在您未定义的类型中?否则,我会看到类似带有枚举的接口这样的接口更合适吗?
维克托里奥·贝拉

@victorio,您好,我不确定枚举将如何解决该问题,如果您错过键,您不会在枚举中遇到错误。这只是键和值之间的映射。
superluminary

1
我明白你的意思了。来自C#,我们没有聪明的方法可以做到这一点。最接近的是的字典Dictionary<enum, additional_metadata>。记录类型是表示该枚举+元数据模式的好方法。
维克托里奥·贝拉
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.