Какой тип записи в машинописи?


183

Что 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

И Advanced Типы страницы упоминается Recordпод отображенных типов заголовков вместе 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}

6
Ответ на 4. в значительной степени "да", так что, вероятно, следует ответить на другие ваши вопросы.
— jcalz

Ответы:


212
  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
Очень многому научился и вопрос, почему «идиоматический способ сделать это - использовать подпись индекса», а не запись? Я не нахожу никакой связанной информации об этом "идиоматическом пути".
— легенды 80

2
Вы можете использовать, Record<string, V>чтобы означать, {[x: string]: V}если вы хотите; Я, наверное, даже сделал это сам. Версия подписи индекса более прямолинейна: они одного типа, но первая является псевдонимом типа сопоставленного типа, который оценивает подпись индекса, тогда как последняя напрямую является только подписью индекса. При прочих равных я бы порекомендовал последнее. Точно так же я бы не использовал Record<"a", string>вместо, {a: string}если бы не было какой-то другой убедительной контекстной причины, чтобы сделать это.
— jcalz

1
«При прочих равных, я бы порекомендовал последнее. » Почему это? Я сам с предварительным Typescript согласен, но я знаю, что первый будет больше, например, самокомментированием для людей, пришедших со стороны C #, и не хуже для сценариев JavaScript-to-Types. Вы просто интересуетесь пропуском шага транспиляции для этих конструкций?
— Ерф

1
Просто мое мнение: поведение Record<string, V>имеет смысл, только если вы уже знаете, как подписи индекса работают в TypeScript. Например, учитывая x: Record<string, string>, x.fooочевидно, будет stringво время компиляции, но в действительности, вероятно, будет string | undefined. Это пробел в том, как --strictNullChecksработает (см. # 13778 ). Я предпочел бы иметь дело с новичками {[x: string]: V}непосредственно вместо того , чтобы ожидать их по цепочке от Record<string, V>по {[P in string]: V}поведению индекса подписи.
— Jcalz

Я хотел указать, что логически тип может быть определен как набор всех значений, содержащихся в типе. учитывая эту интерпретацию, я думаю, что Record <string, V> является разумной абстракцией для упрощения кода вместо того, чтобы выкладывать все возможные значения. Это похоже на пример в документации типов утилит: Запись <T, V>, где тип T = 'a' | 'б' | «с». Никогда не делайте Record <'a', string>, это не хороший контрпример, потому что он не следует той же схеме. Это также не добавляет возможности повторного использования или упрощения кода посредством абстракции, как это делают другие примеры.
— Скотт Леонард

69

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

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

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]} />

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

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


Я предполагаю, что большую часть времени type Statusesживет в типах, не определенных вами? В противном случае я вижу что-то вроде интерфейса с enum, который лучше подходит, верно?
— Викторио Берра

Привет @victorio, я не уверен, как enum решит проблему, вы не получите ошибку в enum, если пропустите ключ. Это просто отображение между ключами и значениями.
— Сверхсветовой

1
Теперь я понимаю, что вы имеете в виду. Исходя из C # у нас нет умных способов сделать это. Самым близким был бы словарь Dictionary<enum, additional_metadata>. Тип Record - отличный способ представить этот шаблон enum + metadata.
— Викторио Берра
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.