Jaki jest typ rekordu w maszynopisie?


182

Co Record<K, T>oznacza w maszynie?

Typescript 2.1 wprowadził Recordtyp, opisując go na przykładzie:

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

patrz Typescript 2.1

Oraz zaawansowane typy strona wymienia Recordpod odwzorowane Rodzaje tytule obok Readonly, Partiali Pick, w co wydaje się być jego definicja:

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

Tylko do odczytu, Częściowe i Pick są homomorficzne, podczas gdy Record nie. Jedną wskazówką, że Record nie jest homomorficzny, jest to, że nie przyjmuje typu wejściowego do kopiowania właściwości z:

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

I to wszystko. Poza powyższymi cytatami nie ma innej wzmianki o Recordna typescriptlang.org .

pytania

  1. Czy ktoś może podać prostą definicję tego, co Recordjest?

  2. Czy jest to Record<K,T>tylko sposób na powiedzenie „wszystkie właściwości tego obiektu będą miały typ T”? Prawdopodobnie nie wszystkie właściwości, ponieważ Kmają jakiś cel ...

  3. Czy Krodzaj ogólny zabrania dodatkowych kluczy w obiekcie, które nie są K, czy też zezwala na nie i po prostu wskazuje, że ich właściwości nie są przekształcane T?

  4. W podanym przykładzie:

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

Czy to dokładnie to samo co to ?:

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

6
Odpowiedź na 4. brzmi prawie „tak”, więc prawdopodobnie powinno to odpowiedzieć na inne pytania.
jcalz

Odpowiedzi:


210
  1. Czy ktoś może podać prostą definicję tego, co Recordjest?

A Record<K, T>jest typem obiektu, którego klucze właściwości są Ki których wartości właściwości są T. Oznacza to, że keyof Record<K, T>jest równoważne Ki Record<K, T>[K]jest (w zasadzie) równoważne T.

  1. Czy jest to Record<K,T>tylko sposób na powiedzenie „wszystkie właściwości tego obiektu będą miały typ T”? Prawdopodobnie nie wszystkie obiekty, ponieważ Kmają jakiś cel ...

Jak zauważyłeś, Kma na celu ... ograniczenie kluczy właściwości do określonych wartości. Jeśli chcesz akceptować wszystkie możliwe klucze o wartościach łańcuchowych, możesz zrobić coś podobnego Record<string, T>, ale idiomatycznym sposobem jest użycie podpisu indeksu, takiego jak { [k: string]: T }.

  1. Czy Krodzaj ogólny zabrania dodatkowych kluczy w obiekcie, które nie są K, czy też zezwala na nie i po prostu wskazuje, że ich właściwości nie są przekształcane T?

Nie „zabrania” dodatkowych kluczy: w końcu wartość może mieć właściwości, które nie zostały wyraźnie wymienione w jej typie ... ale nie rozpoznałaby, że takie właściwości istnieją:

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

i traktowałby je jako właściwości nadmiarowe, które czasami są odrzucane:

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

i czasami akceptowane:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. W podanym przykładzie:

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

    Czy to dokładnie to samo co to ?:

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

Tak!

Mam nadzieję, że to pomoże. Powodzenia!


1
Bardzo dużo się nauczyłem i pytanie, dlaczego „idiomatycznym sposobem na zrobienie tego jest użycie podpisu indeksu”, a nie rekordu? Nie znajduję żadnych powiązanych informacji na temat tego „idiomatycznego sposobu”.
legend80s

2
Możesz użyć Record<string, V>na myśli, {[x: string]: V}jeśli chcesz; Prawdopodobnie sam to zrobiłem. Wersja podpisu indeksu jest bardziej bezpośrednia: są tego samego typu, ale pierwsza jest aliasem typu odwzorowanego typu, którego wynikiem jest podpis indeksu, podczas gdy druga jest po prostu podpisem indeksu bezpośrednio. Ponieważ wszystko inne jest równe, polecam to drugie. Podobnie nie użyłbym Record<"a", string>zamiast, {a: string}chyba że byłby jakiś inny ważny kontekst kontekstowy, aby to zrobić.
jcalz

1
Wszystko inne jest równe, polecam to drugie. ” Dlaczego tak jest? Moje własne przed Typescriptem zgadza się z tym, ale wiem, że ten pierwszy będzie bardziej, hm, samokomentujący się na przykład dla osób pochodzących ze strony C # i nie gorszy dla JavaScript-to-Typescripters. Czy jesteś zainteresowany pominięciem kroku transpilacji dla tych konstrukcji?
ruffin

1
Tylko moja opinia: zachowanie Record<string, V>ma sens tylko wtedy, gdy wiesz już, jak działają podpisy indeksów w TypeScript. Np. Podane x: Record<string, string>, x.foonajwyraźniej będzie stringw czasie kompilacji, ale w rzeczywistości prawdopodobnie tak będzie string | undefined. To jest luka w tym, jak --strictNullChecksdziała (patrz # 13778 ). Wolałbym, aby nowicjusze zajmowali się {[x: string]: V}bezpośrednio, zamiast oczekiwać, że będą śledzić łańcuch od Record<string, V>przejścia {[P in string]: V}do zachowania podpisu indeksu.
jcalz

Chciałem zwrócić uwagę, że logicznie typ można zdefiniować jako zbiór wszystkich wartości zawartych w typie. biorąc pod uwagę tę interpretację, myślę, że Record <string, V> jest rozsądne jako abstrakcja, aby uprościć kod zamiast przedstawiać wszystkie możliwe wartości. Jest podobny do przykładu w dokumentacji typów narzędzi: Record <T, V> gdzie typ T = 'a' | „b” | 'do'. Nigdy nie robienie Record <'a', string> nie jest dobrym kontrprzykładem, ponieważ nie ma tego samego wzorca. Nie przyczynia się również do ponownego użycia lub uproszczenia kodu poprzez abstrakcję, jak robią to inne przykłady.
Scott Leonard

68

Rekord umożliwia utworzenie nowego typu z Unii. Wartości w Unii są używane jako atrybuty nowego typu.

Na przykład powiedz, że mam taką Unię:

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

Teraz chcę utworzyć obiekt, który zawiera informacje o wszystkich kotach, mogę utworzyć nowy typ, używając wartości w CatName Union jako kluczy.

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

Jeśli chcę spełnić wymagania tej CatList, muszę utworzyć taki obiekt:

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

Otrzymujesz bardzo silne bezpieczeństwo typu:

  • Jeśli zapomnę kota, pojawia się błąd.
  • Jeśli dodam niedozwolonego kota, pojawia się błąd.
  • Jeśli później zmienię CatNames, pojawi się błąd. Jest to szczególnie przydatne, ponieważ CatNames jest prawdopodobnie importowane z innego pliku i prawdopodobnie używane w wielu miejscach.

Przykład reakcji w świecie rzeczywistym.

Użyłem tego ostatnio do stworzenia komponentu Status. Komponent otrzyma właściwość stanu, a następnie wyrenderuje ikonę. Bardzo uprościłem kod w celach ilustracyjnych

Miałem taki związek:

type Statuses = "failed" | "complete";

Użyłem tego do stworzenia takiego obiektu:

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

Mogłem wtedy renderować, rozkładając element z obiektu na rekwizyty, na przykład:

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

Jeśli unia Statusy zostanie później rozszerzona lub zmieniona, wiem, że mój składnik Status nie będzie się skompilował i otrzymam błąd, który mogę natychmiast naprawić. Dzięki temu mogę dodać dodatkowe stany błędów do aplikacji.

Zauważ, że rzeczywista aplikacja miała dziesiątki stanów błędów, do których odwoływały się w wielu miejscach, więc tego typu bezpieczeństwo było niezwykle przydatne.


Zakładam, że większość czasu type Statusesżyje w typach NIE zdefiniowanych przez Ciebie? W przeciwnym razie widzę coś w rodzaju interfejsu z wyliczeniem, które jest lepiej dopasowane, prawda?
Victorio Berra

Cześć @ wiktorio, nie jestem pewien, jak wyliczenie rozwiązałoby problem, nie otrzymasz błędu w wyliczeniu, jeśli przegapisz klucz. To tylko mapowanie między kluczami i wartościami.
superluminary

1
Teraz rozumiem, co masz na myśli. W C # nie mamy na to sprytnych sposobów. Najbliższą rzeczą byłby słownik Dictionary<enum, additional_metadata>. Typ rekordu to świetny sposób na reprezentowanie tego wzorca metadanych wyliczenia +.
Victorio Berra
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.