Объекты 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 () {}; класс Customer {} var map: {[email: string]: Customer; знак равно 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> = {};
orellabac
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");

Это само по себе имеет несколько преимуществ перед использованием подобного карте объекта, такого как:

  • Поддержка нестроковых ключей, например чисел или объектов, ни один из которых не поддерживается 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
@Lion - ну да, Mapсериализация довольно забавная. Я, например, выполняю преобразование в объекты пары ключ-значение перед сериализацией, а затем обратно (например, объект из { key: "John", value: { firstName: "John" } }).
Джон Вайс
2
Я сделал ошибку, используя карту вместо простого старого объекта, и сериализация действительно меня поняла. Держись подальше, на мой взгляд.
user378380
1
Это прекрасно. Так рада, что ты вдохновил меня, наконец, окунуться в карты. Это в значительной степени заменит мои обычные структуры раскладок / словарей, поскольку намного проще строго набирать ключи.
Methodician
77

Вы можете использовать шаблоны интерфейсов, как это:

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

let dict: Map<number> = {};
dict["one"] = 1;
Димитар Мажлеков
источник
7
Обратите внимание, что это противоречит типу карты es6. Лучше, чем другой ответ, потому что ограничение индекса игнорируется.
Старый Бадман Грей
Как проверить, существует ли ключ в словаре?
Самнерик
dict.hasOwnProperty ('ключ')
Димитр
1
Я использую словарь вместо карты, чтобы избежать путаницы, и вы можете использовать буквенное обозначение объекта:let dict: Dictionary<number> = { "one": 1, "two": 2 };
PhiLho
8

Вы также можете использовать тип записи в машинописи:

export interface nameInterface { 
    propName : Record<string, otherComplexInterface> 
}
Twen
источник
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

Пример (отображение между перечислением AppointmentStatus и некоторыми метаданными):

  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- или это имеет значение?
Дренаи
@ Дренаи не имеет значения, это то, что вы предпочитаете
Ник Н.
-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);
  });
Джон
источник