Record<K, V> to wbudowany typ narzędziowy dla obiektu, którego klucze mają typ K, a wszystkie wartości typ V. Z kluczami string opisuje słownik; z unią kluczy literałowych opisuje obiekt, który musi mieć dokładnie te klucze.
Record istnieje tylko w systemie typów. W czasie działania oba obiekty to zwykłe obiekty JavaScript, więc działają z literałami obiektów, rozkładaniem, JSON.stringify i wszystkim innym, co przyjmuje obiekt.
Składnia i definicja
Record<Keys, Value>
Keys musi być czymś, co może być kluczem obiektu: string, number, symbol, unią literałów tekstowych lub liczbowych albo template literal type. Value może być dowolnym typem. Cała definicja w bibliotece standardowej TypeScriptu to jedna linia, typ mapowany:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
keyof any to string | number | symbol, zbiór wszystkich możliwych typów kluczy. [P in K]: T tworzy jedną właściwość typu T dla każdego elementu K. To wyjaśnia oba zachowania opisane niżej: szeroki K, taki jak string, daje sygnaturę indeksu (dowolny klucz), a K będący unią daje jedną wymaganą właściwość na każdy element.
Klucze z unii: każdy klucz jest wymagany
Gdy klucze są unią literałów, Record musi zawierać każdy z nich i żadnego innego. To zamienia kompilator w listę kontrolną:
Dodaj "cancelled" do Status, a oba obiekty przestaną się kompilować, dopóki nie dasz nowemu statusowi etykiety i koloru. Pominięcie klucza albo dodanie takiego, którego nie ma w unii, to błąd kompilacji:
index.ts(4,7): error TS2741: Property 'error' is missing in type '{ idle: string; loading: string; success: string; }' but required in type 'Record<Status, string>'.
index.ts(11,54): error TS2353: Object literal may only specify known properties, and 'paused' does not exist in type 'Record<Status, string>'.
To samo działa z enumem tekstowym jako typem klucza: Record<Color, string> wymaga jednego wpisu na każdy element enuma.
Record<string, T> i brakujący klucz
Przy kluczach string dozwolony jest dowolny klucz, a TypeScript typuje każdy odczyt jako T, nawet dla klucza, którego nie ma. W czasie działania brakujący klucz daje undefined:
To najczęstszy błąd z Record. Są trzy sposoby, żeby sobie z nim poradzić: sprawdź przez in lub Object.hasOwn przed odczytem, zadeklaruj wartość jako V | undefined albo włącz opcję kompilatora noUncheckedIndexedAccess, która dodaje | undefined do każdego odczytu przez sygnaturę indeksu w projekcie. Record z kluczami z unii nie ma tego problemu, bo każdy klucz na pewno istnieje.
Partial<Record<K, V>>: tylko niektóre klucze
Aby użyć unii kluczy, ale nie wymagać wszystkich, opakuj Record w Partial. Odczyty zwracają wtedy V | undefined, co jest uczciwe:
Błędnie wpisany klucz, taki jak jp, nadal jest błędem, i to przewaga nad Record<string, string>.
Iterowanie po Record
Object.keys, Object.values i Object.entries działają. Haczyk tkwi w typie klucza: Object.keys zwraca string[], a Object.entries zwraca [string, V][], nigdy twoją unię kluczy:
TypeScript celowo zostawia klucze jako string: obiekt może mieć w czasie działania więcej właściwości, niż wymienia jego typ, więc obietnica Plan[] byłaby ogólnie niebezpieczna. Dla obiektu utworzonego z literału, jak seats, rzutowanie jest bezpieczne.
Budowanie Record z danych
Record to typowy wynik grupowania lub indeksowania tablicy. Zacznij od pustego obiektu o typie Record i wypełnij go:
Book["genre"] używa unii z interfejsu jako typu klucza, więc dodanie gatunku do Book sprawia, że byGenre wymaga nowego wpisu.
Record<string, unknown> i interfejsy
Record<string, unknown> to popularny typ dla "jakiegoś obiektu z kluczami tekstowymi". Przyjmuje literały obiektów i wartości otypowane aliasem type, ale interfejs zostaje odrzucony:
index.ts(11,11): error TS2345: Argument of type 'User' is not assignable to parameter of type 'Record<string, unknown>'.
Index signature for type 'string' is missing in type 'User'.
Interfejsy można rozszerzać przez łączenie deklaracji, więc TypeScript nie zakłada, że pasują do sygnatury indeksu; aliasów typów nie można ponownie otworzyć, więc type User = { name: string } by przeszedł. Typowe rozwiązania to przyjmować zamiast tego object (nadal możesz wywołać na nim Object.keys), uczynić funkcję generyczną (<T extends object>(obj: T)) albo zadeklarować User przez type (strona interface vs type omawia pozostałe różnice).
Record a sygnatura indeksu a Map
Record<K, V> | { [key: string]: V } | Map<K, V> | |
|---|---|---|---|
| Istnieje w czasie działania | nie, zwykły obiekt | nie, zwykły obiekt | tak, klasa |
| Stały zbiór kluczy | tak, z unią K | nie | nie |
| Typy kluczy | string, number, symbol, unie literałów, wzorce template | string, number, symbol, wzorce template | cokolwiek, także obiekty |
| Typ odczytu brakującego klucza | V (z kluczami string) | V | V | undefined z get |
| Łączenie z nazwanymi właściwościami | przez przecięcie & | tak, w tym samym typie | nie |
| JSON i rozkładanie | tak | tak | nie, najpierw konwersja |
| Rozmiar | Object.keys(r).length | Object.keys(o).length | m.size |
| Częste dodawanie i usuwanie | działa | działa | zaprojektowana do tego |
Wybieraj Record z kluczami z unii zawsze, gdy zbiór kluczy jest znany: to jedyna opcja, która sprawdza obecność każdego klucza. Dla otwartych kluczy tekstowych Record<string, V> i sygnatura indeksu są wymienne, a wiele projektów woli Record dla czytelności. Sięgnij po Map, gdy klucze są dodawane i usuwane w czasie działania, gdy klucze nie są stringami albo gdy potrzebujesz rozmiaru i kolejności wstawiania bez dodatkowej pracy.
Najczęściej zadawane pytania
Czym jest Record w TypeScript?
Record<K, V> to wbudowany typ narzędziowy dla obiektu, którego klucze są typu K, a wszystkie wartości typu V. Record<string, number> to obiekt z dowolnymi kluczami tekstowymi i wartościami liczbowymi; Record<"en" | "de", string> to obiekt z dokładnie kluczami en i de, oba typu string.
Czym różni się Record od Map w TypeScript?
Record to typ dla zwykłego obiektu JavaScript, więc działa z literałami obiektów, JSON-em i rozkładaniem, a znika podczas kompilacji. Map to klasa istniejąca w czasie działania, z get, set, has i size, zachowuje kolejność wstawiania wszystkich kluczy, przyjmuje klucze dowolnego typu (także obiekty), a get zwraca V | undefined. Używaj Record dla danych stałych lub w kształcie JSON-a, a Map dla kluczy dodawanych i usuwanych w czasie działania.
Czym różni się Record<string, T> od { [key: string]: T }?
Dla wartości to ten sam typ: Record<string, T> rozwija się do typu obiektowego z sygnaturą indeksu string. Dwie drobne różnice: sygnatura indeksu może mieć nazwę i stać obok innych właściwości w tym samym typie, a keyof Record<string, T> to string, podczas gdy keyof { [key: string]: T } to string | number.
Jak iterować po Record w TypeScript?
Użyj Object.entries(record) dla par klucz-wartość, Object.keys dla kluczy i Object.values dla wartości. Klucze wracają jako string, a nie K, bo obiekt może mieć w czasie działania dodatkowe klucze. Gdy Record ma unię znanych kluczy, rzutuj: (Object.keys(r) as Array<keyof typeof r>).
Jak sprawić, by tylko niektóre klucze Record były wymagane?
Przy kluczach z unii Record<K, V> wymaga każdego klucza. Opakuj go w Partial, żeby wszystkie były opcjonalne: Partial<Record<Lang, string>>. Dla mieszanki użyj przecięcia: Record<"en", string> & Partial<Record<"de" | "fr", string>> wymaga en i dopuszcza pozostałe.