Objets TypeScript comme types de dictionnaire comme en C #

336

J'ai du code JavaScript qui utilise des objets comme dictionnaires; par exemple, un objet "personne" contiendra des informations personnelles saisies sur l'adresse e-mail.

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

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

Est-il possible de décrire cela dans Typescript? ou dois-je utiliser un tableau?

Robert Taylor
la source
5
Ancien poste mais notez qu'il y a la carte ES6
Old Badman Grey

Réponses:

547

Dans les versions plus récentes de tapuscrit, vous pouvez utiliser:

type Customers = Record<string, Customer>

Dans les anciennes versions, vous pouvez utiliser:

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

Vous pouvez également créer une interface si vous ne voulez pas taper cette annotation de type entière à chaque fois:

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

var map: StringToCustomerMap = { };
// Equivalent to first line of above
Ryan Cavanaugh
la source
2
C'est un moyen utile de s'assurer que le compilateur limite les index aux chaînes. Intéressant. Il ne semble pas que vous puissiez spécifier un type d'index autre que des chaînes ou des entiers, mais cela a du sens, car il est simplement mappé aux index d'objets JS natifs.
Ken Smith
5
Vous le savez peut-être, mais il existe également des problèmes potentiels avec cette approche, le plus important étant qu'il n'y a pas de moyen sûr et facile de parcourir tous les membres. Ce code, par exemple, montre qu'il mapcontient deux membres: (<any> Object.prototype) .something = function () {}; classe Client {} var map: {[email: string]: Client; } = {}; map ['[email protected] '] = nouveau client (); pour (var i dans la carte) {console.log (map [i])}
Ken Smith
5
comment en retirez-vous?
TDaver
24
Une autre approche intéressante est la suivante: interface MapStringTo <T> {[clé: chaîne]: T; } Et déclarer la variable commevar map:MapStringTo<Customer> = {};
orellabac
1
Notez que la contrainte d'index ne fonctionne plus. Lire la suite.
David Sherret
127

En plus d'utiliser un objet de type carte , il existe depuis un certain temps un Mapobjet réel , qui est disponible dans TypeScript lors de la compilation vers ES6, ou lors de l'utilisation d'un polyfill avec les définitions de type ES6 :

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

Il prend en charge les mêmes fonctionnalités que Object, et plus, avec une syntaxe légèrement différente:

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

Cela seul a plusieurs avantages par rapport à l'utilisation d'un objet de type carte , comme:

  • Prise en charge des clés non basées sur des chaînes, par exemple des nombres ou des objets, dont aucun n'est pris en charge par Object(non, Objectne prend pas en charge les nombres, il les convertit en chaînes)
  • Moins de place pour les erreurs lors de la non-utilisation --noImplicitAny, car a Maptoujours un type de clé et un type de valeur , alors qu'un objet peut ne pas avoir de signature d'index
  • La fonctionnalité d'ajout / suppression d'éléments (paires clé-valeur) est optimisée pour la tâche, contrairement à la création de propriétés sur unObject

De plus, un Mapobjet fournit une API plus puissante et élégante pour les tâches courantes, dont la plupart ne sont pas disponibles via de simples Objects sans pirater ensemble les fonctions d'assistance (bien que certaines d'entre elles nécessitent un itérateur ES6 complet / polyfill itérable pour les cibles ES5 ou inférieures):

// 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
la source
2
C'est génial! Mais malheureusement, il s'est mal utilisé en série JSON.stringify(), il peut donc être utilisé par exemple pour socket.io :(
Lion
@Lion - eh bien oui, la Mapsérialisation est plutôt drôle. Pour ma part, j'effectue une conversion en objets paire-valeur-clé avant de sérialiser, puis en arrière (par exemple, objet de { key: "John", value: { firstName: "John" } }).
John Weisz
2
J'ai fait l'erreur d'utiliser une carte au lieu d'un simple vieil objet, et la sérialisation m'a vraiment attiré. Restez clair à mon avis.
user378380
1
C'est beau. Je suis tellement content que vous m'ayez inspiré pour enfin puiser dans les cartes. Cela remplacera à peu près mes structures habituelles de clavier / dictionnaire car il est beaucoup plus facile de taper fortement les clés.
Méthodicien
77

Vous pouvez utiliser des interfaces modèles comme ceci:

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

let dict: Map<number> = {};
dict["one"] = 1;
Dimitar Mazhlekov
la source
7
Notez que cela entre en collision avec le type de carte es6. Mieux que l'autre réponse car la contrainte d'index est ignorée.
Old Badman Gray
comment vérifier si une clé existe dans le dictionnaire?
samneric
dict.hasOwnProperty ('key')
Dimitar Mazhlekov
1
J'utilise Dictionary au lieu de Map pour éviter toute confusion, et vous pouvez utiliser la notation d'objet littéral:let dict: Dictionary<number> = { "one": 1, "two": 2 };
PhiLho
8

Vous pouvez également utiliser le type d'enregistrement en tapuscrit:

export interface nameInterface { 
    propName : Record<string, otherComplexInterface> 
}
Twen
la source
5

Lodash a une implémentation de dictionnaire simple et un bon support TypeScript

Installez Lodash:

npm install lodash @types/lodash --save

Importation et utilisation:

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

Vous pouvez utiliser Recordpour cela:

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

Exemple (mappage entre l'énumération AppointmentStatus et certaines métadonnées):

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

Maintenant avec l'interface comme valeur:

interface Icon { Name: string Color: string }

Usage:

const icon: SemanticIcon = iconMapping[appointment.Status]

Nick N.
la source
C'est très utile. Souhaitez-vous utiliser une chaîne enumou un class/objectfor AppointmentStatus- ou est-ce important?
Drenai
@Drenai n'a pas d'importance, c'est ce que vous préférez
Nick N.
-2

Il existe une bibliothèque qui fournit des collections fortement typées et interrogeables en caractères dactylographiés.

Les collections sont:

  • liste
  • dictionnaire

La bibliothèque s'appelle ts-generic-collections . ( Code source sur GitHub )

Vous pouvez créer un dictionnaire et l'interroger comme ci-dessous:

  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);
  });
John
la source