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


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.