Об'єкти TypeScript як типи словників, як у C #


336

У мене є код JavaScript, який використовує об'єкти як словники; наприклад, об’єкт "person" міститиме деякі особисті дані, відкреслені від адреси електронної пошти.

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

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

Чи можна описати це в Typescript? чи мені потрібно використовувати масив?


5
Стара публікація, але зауважте, що там карта ES6
Old Badman Grey

Відповіді:


547

У нових версіях машинопису ви можете використовувати:

type Customers = Record<string, Customer>

У старих версіях ви можете використовувати:

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

Ви також можете створити інтерфейс, якщо ви не хочете кожен раз вводити ці примітки щодо приміток:

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

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

2
Це корисний спосіб переконатися, що компілятор обмежує індекси рядками. Цікаво. Не схоже на те, що ви можете вказати тип індексу як інший, ніж рядки чи цілі числа, але це має сенс, оскільки він просто відображається до нативного індексу JS об'єктів.
Кен Сміт

5
Ви можете це знати, але є й певні потенційні проблеми при такому підході, головне - це те, що немає безпечного та простого способу пройти ітерацію через усіх членів. Наприклад, цей код показує, що mapмістить два члени: (<any> Object.prototype) .something = function () {}; Клієнт класу {} var map: {[email: string]: Клієнт; } = {}; map ['foo@gmail.com '] = новий Клієнт (); for (var i in map) {console.log (map [i])}
Кен Сміт

5
як ви його видаляєте?
TDaver

24
Ще один цікавий підхід: інтерфейс MapStringTo <T> {[key: string]: T; } І var map:MapStringTo<Customer> = {};
оголосимо

1
Зверніть увагу, що обмеження індексу більше не працює. Детальніше
Девід Шеррет

127

У доповненні до використання Map- як об'єкт, там був фактичний Mapоб'єкт в протягом деякого часу, яке є в друкованому при компіляції в ES6, або при використанні polyfill з ES6 типовими визначеннями :

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

Він підтримує той же функціонал Object, що і більше, з дещо іншим синтаксисом:

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

Це саме по собі має ряд переваг по порівнянні з використанням Map- як об'єкта, такі як:

  • Підтримка ключів на основі рядків, наприклад, чисел чи об'єктів, жоден з яких не підтримується Object(ні, Objectне підтримує числа, він перетворює їх у рядки)
  • Менше місця для помилок, коли не використовується --noImplicitAny, оскільки Mapзавжди є тип ключа та тип значення , тоді як об'єкт може не мати підпису індексу
  • Функціональність додавання / вилучення елементів (пари ключових значень) оптимізована для завдання, на відміну від створення властивостей наObject

Крім того, Mapоб’єкт надає більш потужний та елегантний API для загальних завдань, більшість з яких доступні не за допомогою простих Objects без злому допоміжних функцій (хоча для деяких з них потрібен повний ітератор / ітерабельний поліфайл ES6 для цілей ES5 або нижче):

// 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
Це круто! Але, на жаль, він неправильно серіалізував використання JSON.stringify(), тому його можна використовувати, наприклад, для socket.io :(
Лев

@Lion - ну так, Mapсеріалізація досить кумедна. Я, наприклад, здійснюю перетворення на об'єкти пари "ключ-значення" перед серіалізацією, а потім назад (наприклад, об'єкт { key: "John", value: { firstName: "John" } }).
Джон Вайс

2
Я допустив помилку, використовуючи карту замість простого старого об’єкта, і серіалізація насправді отримала мене. По-моєму, чітко.
користувач378380

1
Це прекрасно. Тож радий, що ти надихнув мене нарешті зануритися у карти. Це в значній мірі замінить мої звичні структури клавіатури / словника, оскільки сильно набрати клавіші набагато простіше.
Methodician

77

Ви можете використовувати такі шаблонні інтерфейси:

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

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

7
Зауважте, що це стикається з типом карти es6. Краще, ніж інша відповідь, оскільки обмеження індексу ігнорується.
Старий Бадман Грей

як перевірити, чи існує ключ у словнику?
самнерік

dict.hasOwnProperty ('ключ')
Димитар Мажлеков

1
Я використовую Словник замість Map, щоб уникнути плутанини, і ви можете використовувати буквальні позначення об'єкта:let dict: Dictionary<number> = { "one": 1, "two": 2 };
PhiLho

8

Ви також можете використовувати тип запису в машинописному тексті:

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

5

Lodash має просту реалізацію словника та має гарну підтримку TypeScript

Встановити Lodash:

npm install lodash @types/lodash --save

Імпорт та використання:

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

3

Ви можете використовувати Recordдля цього:

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

Приклад (Зображення між перерахунком AptribuStatus та деякими метаданими):

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

Тепер з інтерфейсом як значення:

interface Icon { Name: string Color: string }

Використання:

const icon: SemanticIcon = iconMapping[appointment.Status]


Це дуже корисно. Чи використовуєте ви рядок enumабо class/objectдля AppointmentStatus- чи це має значення?
Дренай

@Drenai не має значення, це те, що ви віддаєте перевагу
Нік Н.

-2

Існує бібліотека, яка забезпечує колекціоновані колекції з сильним типом в машинопис.

Колекції:

  • Список
  • Словник

Бібліотека називається ts-generic-collection . ( Вихідний код на GitHub )

Ви можете створити словник і запитувати його, як показано нижче:

  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);
  });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.