Что такое тип записи в машинописном тексте?

Что означает 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>

см. Typescript 2.1

А на странице " Расширенные типы" упоминается " Record под заголовком " Readonly типы" наряду с " Readonly, " Partial и " Pick, что, как представляется, является ее определением:

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

Только для чтения, Partial и Pick гомоморфны, а Record - нет. Один признак того, что Record не является гомоморфным, заключается в том, что для копирования свойств не требуется тип ввода:

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

И это оно. Помимо приведенных цитат, нет других упоминаний о Record на typescriptlang.org.

Вопросы

  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}
    

Ответы

Ответ 1

  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 generic дополнительные ключи на объекте, который не является 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}
    

Да!

Надеюсь, это поможет. Удачи!

Ответ 2

Запись позволяет вам создать новый тип из Союза. Значения в Союзе используются в качестве атрибутов нового типа.

Например, скажем, у меня есть Союз, подобный этому:

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

Теперь я хочу создать объект, который содержит информацию обо всех кошках, я могу создать новый тип, используя значения в Союзе CatName в качестве ключей.

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

Затем я могу сделать рендеринг, разложив элемент из объекта в подпорки, например, так:

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

Если объединение "Статусы" будет позже расширено или изменено, я знаю, что мой компонент "Статус" не удастся скомпилировать, и я получу ошибку, которую можно исправить немедленно. Это позволяет мне добавлять дополнительные состояния ошибок в приложение.

Обратите внимание, что в настоящем приложении были десятки состояний ошибок, на которые ссылались в нескольких местах, поэтому безопасность этого типа была чрезвычайно полезной.