Menu

Słownik w TypeScript: sygnatury indeksu, Record i Map

TypeScript nie ma klasy słownika ani hashmapy; wyszukiwanie klucz-wartość typujesz sygnaturą indeksu, Record<K, V> albo Map<K, V>. Poznaj każde z nich, sprawdzanie, czy klucz istnieje, dodawanie, usuwanie i iterację oraz powód, dla którego noUncheckedIndexedAccess ma znaczenie przy słownikach.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

TypeScript nie ma osobnej klasy słownika ani hashmapy. Słownik to albo zwykły obiekt otypowany sygnaturą indeksu, { [key: string]: number }, albo ten sam typ zapisany jako Record<string, number>, albo Map<string, number>. Wszystkie trzy przechowują wartości po kluczu; różnią się typami kluczy, sposobem typowania brakujących kluczy i serializacją.

Dla kluczy napisowych i danych w kształcie JSON zwykłym wyborem jest obiekt z Record<string, T>. Dla kluczy innych niż napisy albo wpisów ciągle dodawanych i usuwanych użyj Map.

Sygnatury indeksu

Sygnatura indeksu, [key: KeyType]: ValueType, mówi „każdy klucz tego typu wskazuje wartość tego typu”. Nazwa klucza (key, name, userId) to tylko dokumentacja. Typy kluczy mogą być string, number, symbol, wzorcami szablonów literałów albo uniami tych typów.

JavaScript zamienia klucze liczbowe na napisy, więc obiekt { [id: number]: string } nadal ma w czasie działania klucze napisowe: Object.keys({ 1: "one" }) to [ '1' ]. Liczbowa sygnatura indeksu ogranicza tylko to, jak możesz go indeksować w TypeScript.

Record<K, V>

Record<string, V> to skrót od { [key: string]: V }. Z unią kluczy literałowych zamiast string staje się stałym słownikiem, który musi zawierać każdy klucz:

Pominięcie staging w urls to błąd kompilacji TS2741 (Property 'staging' is missing...), dzięki czemu Record z kluczami w postaci unii to sprawdzana tablica wyszukiwania. Partial sprawia, że każda wartość ma typ number | undefined.

Map jako hashmapa

Map przyjmuje klucze dowolnego typu, zachowuje kolejność wstawiania, ma size i uczciwie typuje brakujące klucze: get zwraca V | undefined.

Sprawdzanie, czy klucz istnieje

Istnieje kilka sprawdzeń i nie wszystkie znaczą to samo:

SprawdzenieDziała naUważaj na
Object.hasOwn(obj, key)obiektachES2022; na starszych targetach użyj Object.prototype.hasOwnProperty.call(obj, key)
key in objobiektachPrawdziwe także dla odziedziczonych kluczy, takich jak toString i constructor
obj[key] !== undefinedobiektachNie odróżnia brakującego klucza od klucza z zapisanym undefined
if (obj[key])obiektachFałszywe także dla wartości 0, "" i false
map.has(key)MapNie zawęża późniejszego map.get(key)
map.get(key) !== undefinedMapTo samo zastrzeżenie o undefined co przy obiektach

Problem z odziedziczonymi kluczami sprawia, że klucze podawane przez użytkowników, takie jak "constructor" albo "__proto__", czynią zwykłe obiekty ryzykownymi jako słowniki. Map nie ma takich kluczy.

Problem z typem brakującego klucza

Na sygnaturze indeksu albo Record<string, T> odczyt dowolnego klucza ma typ T, nawet klucza, który nie istnieje. Kompilator pozwala wywoływać metody na wartości, która w czasie działania jest undefined:

Opcja kompilatora noUncheckedIndexedAccess to naprawia: z nią colors["grass"] ma typ string | undefined, a wywołanie toUpperCase to błąd kompilacji, dopóki go nie sprawdzisz. Nie jest częścią strict, więc trzeba ją włączyć osobno w tsconfig.json; inne flagi obok niej opisuje strona o trybie strict. Map nie ma tej luki, bo get zawsze uwzględnia undefined.

Dodawanie, usuwanie i iteracja

delete działa na właściwościach z sygnatury indeksu. Na wymaganej nazwanej właściwości typu obiektowego to błąd kompilacji TS2790, The operand of a 'delete' operator must be optional.

Czego użyć

PotrzebaUżyj
Klucze napisowe, JSON na wejściu lub wyjściuRecord<string, T>
Stały, znany zbiór kluczy, wszystkie wymaganeRecord<"a" | "b", T>
Nazwane właściwości plus dowolne dodatkowe kluczetyp obiektowy z sygnaturą indeksu
Klucze będące obiektami, liczbami trzymanymi jako liczby albo czymkolwiek innym niż napisMap<K, V>
Częste dodawanie i usuwanie albo często odczytywany rozmiarMap<K, V>
Klucze pochodzące od użytkownikówMap<K, V> (brak odziedziczonych kluczy)

Najczęściej zadawane pytania

Jak utworzyć słownik w TypeScript?

Otypuj zwykły obiekt sygnaturą indeksu, const ages: { [name: string]: number } = {}, albo równoważnym Record<string, number>. Potem dodawaj wpisy przez ages["ada"] = 36. Dla kluczy, które nie są napisami, albo kolekcji, do której często dodajesz i z której często usuwasz, użyj new Map<string, number>().

Czy TypeScript ma HashMap?

Nie pod tą nazwą. Wbudowany w JavaScript Map to hashmapa: Map<K, V> przechowuje pary klucz-wartość z szybkim wyszukiwaniem po kluczu, zachowuje kolejność wstawiania i przyjmuje klucze dowolnego typu. Zwykły obiekt otypowany jako Record<string, V> to drugi popularny wybór dla kluczy napisowych.

Jak sprawdzić, czy klucz istnieje w słowniku TypeScript?

Dla słownika w postaci obiektu użyj Object.hasOwn(dict, key) albo key in dict (które widzi też odziedziczone właściwości, takie jak toString), albo odczytaj wartość i porównaj ją z undefined. Dla Map użyj map.has(key) albo sprawdź bezpośrednio wynik map.get(key), bo has nie zawęża późniejszego get.

Czym różni się sygnatura indeksu od Record?

{ [key: string]: T } i Record<string, T> opisują ten sam typ. Record jest krótszy i może też przyjąć unię konkretnych kluczy, Record<"a" | "b", T>, która wymaga każdego klucza. Sygnaturę indeksu można połączyć z nazwanymi właściwościami w jednym typie obiektowym, a jej klucz może mieć nazwę, która go dokumentuje, jak w { [userId: string]: User }.

Dlaczego odczyt brakującego klucza słownika nie daje błędu?

Domyślnie dict[key] na sygnaturze indeksu albo Record<string, T> ma typ T, choć w czasie działania wartość dla brakującego klucza to undefined. Włącz noUncheckedIndexedAccess w tsconfig.json, a typ zmieni się na T | undefined, co wymusi sprawdzenie. strict nie obejmuje tej opcji.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ