Який тип запису в машинописі?


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>

див. Typescript 2.1

І Advanced Типи сторінки згадується Recordпід відображених типів заголовків разом Readonly, Partialі Pick, в тому, що , як видається, його визначення:

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

Readonly, Partial і Pick є гомоморфними, тоді як Record - ні. Один із ключів, що Record не є гомоморфним, - це те, що для копіювання властивостей із:

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

І це все. Окрім вищенаведених цитат, Recordна typecriptlang.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

Відповіді:


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

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

1
"Якщо всі інші рівні, я б рекомендував останнє. " Чому це? Моя перед-Typescript погоджується, але я знаю, що колишнє буде більше, гм, самокоментування для людей, які приходять, наприклад, зі сторони C #, і не гірше для JavaScript-Typescripters. Вам просто цікаво пропустити крок транспіляції для цих конструкцій?
ruffin

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> є розумним як абстракція для спрощення коду, а не викладення всіх можливих значень. Це аналогічно прикладу в документації про типи утиліт: Record <T, V> where type T = 'a' | 'b' | 'c'. Ніколи не робити запис <'a', string> не є хорошим прикладом лічильника, тому що він не відповідає тій же схемі. Він також не додає до повторного використання або спрощення коду шляхом абстрагування, як це роблять інші приклади.
Скотт Леонард

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, ймовірно, імпортується з іншого файлу і, ймовірно, використовується в багатьох місцях.

Приклад реагування в реальному світі

Нещодавно я використовував це для створення компонента Статус. Компонент отримає підтримку статусу, а потім надасть значок. Я досить спростив код тут для ілюстративних цілей

У мене був такий союз:

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

Якщо пізніше союз статусів буде розширено або змінено, я знаю, що компонент Status не вдасться скомпілювати, і я отримаю помилку, яку я можу виправити негайно. Це дозволяє мені додавати додаткові стани помилок у додаток.

Зауважте, що фактичний додаток мав десятки станів помилок, на які посилалося в декількох місцях, тому безпека цього типу була надзвичайно корисною.


Я припускаю, що більшість часу type Statusesживе в типізованих, не визначених вами? Інакше я можу побачити щось на зразок інтерфейсу з перерахунком, який краще підходить?
Вікторіо Берра

Привіт @victorio, я не впевнений, як переслідування вирішить проблему, якщо ти не пропустиш ключ, ти не отримаєш помилку в перерахунку. Це просто зіставлення між ключами та значеннями.
superluminary

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