Obiekty TypeScript jako typy słownikowe jak w C #

336

Mam kod JavaScript, który używa obiektów jako słowników; na przykład obiekt „osoba” będzie przechowywał niektóre dane osobowe zapisane poza adresem e-mail.

var people = {<email> : <'some personal data'>};

adding   > "people[<email>] = <data>;" 
getting  > "var data = people[<email>];" 
deleting > "delete people[<email>];"

Czy można to opisać w maszynopisie? czy muszę użyć tablicy?

Robert Taylor
źródło
5
Stary post, ale pamiętaj, że istnieje mapa ES6
Old Badman Gray

Odpowiedzi:

547

W nowszych wersjach maszynopisu możesz użyć:

type Customers = Record<string, Customer>

W starszych wersjach możesz użyć:

var map: { [email: string]: Customer; } = { };
map['[email protected]'] = new Customer(); // OK
map[14] = new Customer(); // Not OK, 14 is not a string
map['[email protected]'] = 'x'; // Not OK, 'x' is not a customer

Możesz także utworzyć interfejs, jeśli nie chcesz za każdym razem wpisywać adnotacji tego typu:

interface StringToCustomerMap {
    [email: string]: Customer;
}

var map: StringToCustomerMap = { };
// Equivalent to first line of above
Ryan Cavanaugh
źródło
2
Jest to przydatny sposób, aby upewnić się, że kompilator ogranicza indeksy do łańcuchów. Ciekawy. Nie wygląda na to, aby można było określić typ indeksu jako coś innego niż ciągi lub liczby całkowite, ale ma to sens, ponieważ po prostu mapuje na natywne indeksy obiektów JS.
Ken Smith
5
Być może wiesz o tym, ale istnieją również potencjalne problemy z takim podejściem, z tym dużym, że nie ma bezpiecznego i łatwego sposobu na iterację przez wszystkich członków. Na przykład ten kod pokazuje, że mapzawiera dwa elementy: (<any> Object.prototype) .something = function () {}; klasa Klient {} var map: {[email: string]: Klient; } = {}; map ['[email protected]'] = nowy klient (); for (var i in map) {console.log (map [i])}
Ken Smith
5
jak go usunąć?
TDaver
24
Innym interesującym podejściem jest: interfejs MapStringTo <T> {[key: string]: T; } I deklarujemy zmienną jakvar map:MapStringTo<Customer> = {};
orellabac
1
Zwróć uwagę, że ograniczenie indeksu już nie działa. Czytaj więcej.
David Sherret
127

Oprócz korzystania z obiektu podobnego do mapy , od pewnego czasu istnieje już rzeczywisty Mapobiekt , który jest dostępny w TypeScript podczas kompilacji do ES6 lub podczas korzystania z funkcji wypełniania z definicjami typów ES6 :

let people = new Map<string, Person>();

Obsługuje tę samą funkcjonalność co Objecti więcej, z nieco inną składnią:

// Adding an item (a key-value pair):
people.set("John", { firstName: "John", lastName: "Doe" });

// Checking for the presence of a key:
people.has("John"); // true

// Retrieving a value by a key:
people.get("John").lastName; // "Doe"

// Deleting an item by a key:
people.delete("John");

Samo to ma kilka zalet w porównaniu z użyciem obiektu podobnego do mapy , takiego jak:

  • Obsługa kluczy nie opartych na ciągach znaków, np. Liczb lub obiektów, z których żaden nie jest obsługiwany przez Object(nie, Objectnie obsługuje liczb, konwertuje je na ciągi znaków)
  • Mniej miejsca na błędy, gdy nie jest używany --noImplicitAny, ponieważ Mapzawsze ma typ klucza i typ wartości , podczas gdy obiekt może nie mieć podpisu indeksu
  • Funkcjonalność dodawania / usuwania elementów (pary klucz-wartość) jest zoptymalizowana do tego zadania, w przeciwieństwie do tworzenia właściwości w plikuObject

Ponadto Mapobiekt zapewnia bardziej wydajny i elegancki interfejs API do typowych zadań, z których większość nie jest dostępna za pomocą prostych Objects bez hakowania razem funkcji pomocniczych (chociaż niektóre z nich wymagają pełnego iteratora / wielokrotnego wypełniania ES6 dla celów ES5 lub niższych):

// Iterate over Map entries:
people.forEach((person, key) => ...);

// Clear the Map:
people.clear();

// Get Map size:
people.size;

// Extract keys into array (in insertion order):
let keys = Array.from(people.keys());

// Extract values into array (in insertion order):
let values = Array.from(people.values());
John Weisz
źródło
2
To cudownie! Niestety nie udało się go użyć do serializacji JSON.stringify(), więc można go użyć np. Do socket.io :(
Lion
@Lion - no tak, Mapserializacja jest raczej zabawna. Ja, na przykład, wykonuję konwersję do obiektów pary klucz-wartość przed serializacją, a następnie z powrotem (np. Obiekt { key: "John", value: { firstName: "John" } }).
John Weisz
2
Popełniłem błąd, używając mapy zamiast zwykłego starego obiektu, a serializacja naprawdę mnie dopadła. Moim zdaniem kieruj się jasno.
użytkownik378380
1
To jest piękne. Cieszę się, że zainspirowałeś mnie do zanurzenia się w mapy. To prawie zastąpi moje zwykłe struktury map / słowników, ponieważ o wiele łatwiej jest mocno wpisywać klucze.
metodolog
77

Możesz użyć takich szablonów:

interface Map<T> {
    [K: string]: T;
}

let dict: Map<number> = {};
dict["one"] = 1;
Dimitar Mazhlekov
źródło
7
Zauważ, że koliduje to z typem mapy es6. Lepsza niż druga odpowiedź, ponieważ ograniczenie indeksu jest ignorowane.
Old Badman Gray,
jak sprawdzić, czy w słowniku istnieje klucz?
samneric
dict.hasOwnProperty ('key')
Dimitar Mazhlekov
1
Używam słownika zamiast mapy, aby uniknąć pomyłek, i możesz użyć dosłownej notacji obiektowej:let dict: Dictionary<number> = { "one": 1, "two": 2 };
PhiLho
8

Możesz także użyć typu Record w maszynopisie:

export interface nameInterface { 
    propName : Record<string, otherComplexInterface> 
}
Twen
źródło
5

Lodash ma prostą implementację słownika i ma dobrą obsługę TypeScript

Zainstaluj Lodash:

npm install lodash @types/lodash --save

Import i użycie:

import { Dictionary } from "lodash";
let properties : Dictionary<string> = {
    "key": "value"        
}
console.log(properties["key"])
phil
źródło
3

Możesz użyć Recorddo tego:

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkt

Przykład (odwzorowanie między wyliczeniem AppointmentStatus a niektórymi metadanymi):

  const iconMapping: Record<AppointmentStatus, Icon> = {
    [AppointmentStatus.Failed]: { Name: 'calendar times', Color: 'red' },
    [AppointmentStatus.Canceled]: { Name: 'calendar times outline', Color: 'red' },
    [AppointmentStatus.Confirmed]: { Name: 'calendar check outline', Color: 'green' },
    [AppointmentStatus.Requested]: { Name: 'calendar alternate outline', Color: 'orange' },
    [AppointmentStatus.None]: { Name: 'calendar outline', Color: 'blue' }
  }

Teraz z interfejsem jako wartością:

interface Icon { Name: string Color: string }

Stosowanie:

const icon: SemanticIcon = iconMapping[appointment.Status]

Nick N.
źródło
To jest bardzo przydatne. Czy użyłbyś ciągu enumlub class/objectfor AppointmentStatus- czy to ma znaczenie?
Drenai
@Drenai nie ma znaczenia, to jest to, co wolisz
Nick N.
-2

Istnieje biblioteka, która zapewnia silnie typowe, możliwe do zapytania zbiory w maszynopisie.

Kolekcje są:

  • Lista
  • Słownik

Biblioteka nosi nazwę ts-generic-collections . ( Kod źródłowy na GitHub )

Możesz stworzyć słownik i zapytać go jak poniżej:

  it('firstOrDefault', () => {
    let dictionary = new Dictionary<Car, IList<Feature>>();

    let car = new Car(1, "Mercedez", "S 400", Country.Germany);
    let car2 = new Car(2, "Mercedez", "S 500", Country.Germany);

    let features = new List<Feature>();

    let feature = new Feature(1, "2 - Door Sedan");
    features.add(feature);

    dictionary.add(car, features);

    features = new List<Feature>();
    feature = new Feature(2, "4 - Door Sedan");
    features.add(feature);

    dictionary.add(car2, features);

    //query
    let first = dictionary.firstOrDefault(x => x.key.name == "Mercedez");

    expect(first.key.id = 1);
  });
Jan
źródło