Oggetti TypeScript come tipi di dizionario come in C #


336

Ho del codice JavaScript che utilizza oggetti come dizionari; ad esempio un oggetto "persona" conterrà alcuni dettagli personali cancellati dall'indirizzo e-mail.

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

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

È possibile descriverlo in Typescript? o devo usare un array?


5
Vecchio post ma nota che c'è la mappa ES6
Old Badman Gray,

Risposte:


547

Nelle versioni più recenti di dattiloscritto è possibile utilizzare:

type Customers = Record<string, Customer>

Nelle versioni precedenti è possibile utilizzare:

var map: { [email: string]: Customer; } = { };
map['foo@gmail.com'] = new Customer(); // OK
map[14] = new Customer(); // Not OK, 14 is not a string
map['bar@hotmail.com'] = 'x'; // Not OK, 'x' is not a customer

Puoi anche creare un'interfaccia se non vuoi digitare ogni volta l'annotazione dell'intero tipo:

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

var map: StringToCustomerMap = { };
// Equivalent to first line of above

2
Questo è un modo utile per assicurarsi che il compilatore limiti gli indici alle stringhe. Interessante. Non sembra che tu possa specificare che il tipo di indice sia diverso da stringhe o numeri interi, ma ha senso, dato che si limita a mappare gli indici degli oggetti JS nativi.
Ken Smith,

5
Potresti saperlo, ma ci sono anche alcuni potenziali problemi con questo approccio, il più grande è che non esiste un modo sicuro e semplice per scorrere tutti i membri. Questo codice, ad esempio, mostra che mapcontiene due membri: (<any> Object.prototype) .something = function () {}; class Customer {} var map: {[email: string]: Cliente; } = {}; map ['foo@gmail.com '] = nuovo cliente (); per (var i in map) {console.log (map [i])}
Ken Smith,

5
come si rimuove da esso?
TDaver

24
Un altro approccio interessante è: interfaccia MapStringTo <T> {[chiave: stringa]: T; } E dichiarano la variabile likevar map:MapStringTo<Customer> = {};
orellabac il

1
Si noti che il vincolo dell'indice non funziona più. Leggi di più.
David Sherret,

127

Oltre all'utilizzo di un oggetto simile a una mappa , esiste da qualche tempo un Mapoggetto reale , disponibile in TypeScript durante la compilazione su ES6 o quando si utilizza un polyfill con le definizioni di tipo ES6 :

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

Supporta le stesse funzionalità Objecte molto altro, con una sintassi leggermente diversa:

// 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");

Questo da solo ha diversi vantaggi rispetto all'uso di un oggetto simile a una mappa , come ad esempio:

  • Supporto per chiavi non basate su stringhe, ad esempio numeri o oggetti, nessuno dei quali è supportato da Object(no, Objectnon supporta numeri, li converte in stringhe)
  • Meno spazio per gli errori quando non vengono utilizzati --noImplicitAny, poiché Mapsempre ha un tipo di chiave e un tipo di valore , mentre un oggetto potrebbe non avere una firma indice
  • La funzionalità di aggiunta / rimozione di elementi (coppie chiave-valore) è ottimizzata per l'attività, a differenza della creazione di proprietà su unObject

Inoltre, un Mapoggetto fornisce un'API più potente ed elegante per attività comuni, la maggior parte delle quali non sono disponibili tramite semplici messaggi Objectsenza hackerare insieme le funzioni di supporto (sebbene alcune di queste richiedano un iteratore ES6 completo / polifill iterabile per target ES5 o precedenti):

// 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());

2
È fantastico! Ma purtroppo si è sbagliato utilizzando la serializzazione JSON.stringify(), quindi può essere utilizzato ad esempio per socket.io :(
Lion

@Lion - beh sì, la Mapserializzazione è piuttosto divertente. Io, per esempio, eseguo una conversione in oggetti chiave-valore-coppia prima di serializzare, e poi indietro (es. Oggetto di { key: "John", value: { firstName: "John" } }).
John Weisz,

2
Ho fatto l'errore di usare una mappa invece di un semplice vecchio oggetto e la serializzazione mi ha davvero conquistato. Stai alla larga secondo me.
user378380

1
Questo è bellissimo. Sono così felice che mi hai ispirato a immergermi finalmente nelle mappe. Questo sostituirà praticamente la mia solita struttura keymap / dizionario poiché è molto più semplice digitare fortemente le chiavi.
Metodista,

77

Puoi usare interfacce basate su modelli come questa:

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

let dict: Map<number> = {};
dict["one"] = 1;

7
Si noti che questo si scontra con il tipo di mappa es6. Meglio dell'altra risposta perché il vincolo dell'indice viene ignorato.
Old Badman Grey,

come posso verificare se esiste una chiave nel dizionario?
Samneric,

dict.hasOwnProperty ('key')
Dimitar Mazhlekov

1
Uso Dizionario invece di Mappa per evitare confusione, e puoi usare la notazione letterale dell'oggetto:let dict: Dictionary<number> = { "one": 1, "two": 2 };
PhiLho,

8

Puoi anche usare il Tipo di record nel dattiloscritto:

export interface nameInterface { 
    propName : Record<string, otherComplexInterface> 
}

5

Lodash ha una semplice implementazione del dizionario e ha un buon supporto TypeScript

Installa Lodash:

npm install lodash @types/lodash --save

Importazione e utilizzo:

import { Dictionary } from "lodash";
let properties : Dictionary<string> = {
    "key": "value"        
}
console.log(properties["key"])

3

Puoi usare Recordper questo:

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

Esempio (una mappatura tra AppointmentStatus enum e alcuni metadati):

  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' }
  }

Ora con interfaccia come valore:

interface Icon { Name: string Color: string }

Uso:

const icon: SemanticIcon = iconMapping[appointment.Status]


Questo è molto utile Utilizzeresti una stringa enumo un class/objectfor AppointmentStatus- o importa?
Drenai,

@Drenai non importa, è quello che preferisci
Nick N.

-2

Esiste una libreria che fornisce raccolte fortemente tipabili e interrogabili in dattiloscritto.

Le collezioni sono:

  • Elenco
  • Dizionario

La libreria si chiama ts-generic-collections . ( Codice sorgente su GitHub )

È possibile creare un dizionario e interrogarlo come di seguito:

  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);
  });
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.