Objetos TypeScript como tipos de dicionário como em C #

335

Eu tenho algum código JavaScript que usa objetos como dicionários; por exemplo, um objeto 'pessoa' conterá alguns detalhes pessoais digitados no endereço de email.

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

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

É possível descrever isso no texto datilografado? ou eu tenho que usar uma matriz?

Robert Taylor
fonte
5
Post antigo, mas observe que existe o ES6 Map
Old Badman Gray

Respostas:

544

Nas versões mais recentes do texto datilografado, você pode usar:

type Customers = Record<string, Customer>

Nas versões mais antigas, você pode usar:

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

Você também pode criar uma interface se não quiser digitar toda a anotação de tipo sempre:

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

var map: StringToCustomerMap = { };
// Equivalent to first line of above
Ryan Cavanaugh
fonte
2
Essa é uma maneira útil de garantir que o compilador restrinja os índices às strings. Interessante. Não parece que você possa especificar o tipo de índice como algo além de cadeias ou números inteiros, mas isso faz sentido, pois apenas mapeia para os índices de objetos JS nativos.
Ken Smith
5
Você pode saber disso, mas também existem algumas dicas em potencial com essa abordagem, a principal delas é que não há uma maneira fácil e segura de interagir com todos os membros. Este código, por exemplo, mostra que mapcontém dois membros: (<qualquer> Object.prototype) .something = function () {}; classe Customer {} var map: {[email: string]: Customer; } = {}; map ['[email protected] '] = novo cliente (); para {console.log (mapa [i])} (var i no mapa)
Ken Smith
5
como você remove isso?
precisa saber é o seguinte
24
Outra abordagem interessante é: interface MapStringTo <T> {[key: string]: T; } E declarar a variável comovar map:MapStringTo<Customer> = {};
orellabac
11
Observe que a restrição de índice não funciona mais. Consulte Mais informação.
David Sherret
127

Além de usar um objeto parecido com um mapa , há um Mapobjeto real há algum tempo, disponível no TypeScript ao compilar no ES6 ou ao usar um polyfill com as definições de tipo do ES6 :

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

Ele suporta a mesma funcionalidade Objecte mais com uma sintaxe ligeiramente diferente:

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

Isso por si só tem várias vantagens sobre o uso de um objeto parecido com um mapa , como:

  • Suporte para chaves não baseadas em strings, por exemplo, números ou objetos, nenhum dos quais é suportado por Object(não, Objectnão suporta números, ele os converte em strings)
  • Menos espaço para erros quando não estiver em uso --noImplicitAny, pois Mapsempre possui um tipo de chave e um valor , enquanto um objeto pode não ter uma assinatura de índice
  • A funcionalidade de adicionar / remover itens (pares de valores-chave) é otimizada para a tarefa, ao contrário da criação de propriedades em umObject

Além disso, um Mapobjeto fornece uma API mais poderosa e elegante para tarefas comuns, a maioria das quais não está disponível através de Objects simples sem hackear as funções auxiliares (embora algumas delas exijam um iterador ES6 / polyfill iterável para destinos ES5 ou abaixo):

// 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
fonte
2
Fantástico! Mas, infelizmente, ele foi errado serializada usando JSON.stringify(), por isso pode ser por exemplo utilizado para socket.io :(
Lion
@ Lion - bem, sim, a Mapserialização é bastante engraçada. Eu, por exemplo, realizo uma conversão em objetos de par de valores-chave antes de serializar e depois volto (por exemplo, objeto de { key: "John", value: { firstName: "John" } }).
John Weisz
2
Cometi o erro de usar um mapa em vez de um objeto antigo simples, e a serialização realmente me pegou. Fique claro na minha opinião.
user378380
11
Isso é lindo. Que bom que você me inspirou a finalmente mergulhar nos mapas. Isso substituirá praticamente minhas estruturas usuais de mapa de chaves / dicionário, pois é muito mais fácil digitar fortemente as chaves.
Methodician
77

Você pode usar interfaces modeladas como esta:

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

let dict: Map<number> = {};
dict["one"] = 1;
Dimitar Mazhlekov
fonte
7
Observe que isso colide com o tipo de mapa es6. Melhor que a outra resposta porque a restrição de índice é ignorada.
Old Badman Grey
como verifico se existe uma chave no dicionário?
samneric 15/05
dict.hasOwnProperty ('key')
Dimitar Mazhlekov
11
Eu uso dicionário em vez de Mapa à confusão evitar, e você pode usar a notação de objeto literal:let dict: Dictionary<number> = { "one": 1, "two": 2 };
PhiLho
8

Você também pode usar o tipo de registro em texto datilografado:

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

O Lodash possui uma implementação simples do Dicionário e um bom suporte ao TypeScript

Instale o Lodash:

npm install lodash @types/lodash --save

Importação e uso:

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

Você pode usar Record para isso:

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

Exemplo (Um mapeamento entre a enumeração AppointmentStatus e alguns metadados):

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

Agora com a interface como valor:

interface Icon { Name: string Color: string }

Uso:

const icon: SemanticIcon = iconMapping[appointment.Status]

Nick N.
fonte
Isso é muito útil. Você usaria uma string enumou um class/objectfor AppointmentStatus- ou isso importa?
Drenai 27/10/19
Para mim é um enum. Veja minha resposta atualizada.
Nick N.
@Drenai não importa, é o que você preferir
Nick N.
-2

Há uma biblioteca que fornece coleções consultáveis ​​e fortemente tipadas em texto datilografado.

As coleções são:

  • Lista
  • Dicionário

A biblioteca é chamada ts-generic-collections . ( Código fonte no GitHub )

Você pode criar um dicionário e consultá-lo como abaixo:

  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
fonte