Выполнение типа проиндексированных элементов объекта Typescript?
Я хотел бы сохранить сопоставление строки → string в объекте Typescript и обеспечить, чтобы все ключи отображались в строках. Например:
var stuff = {};
stuff["a"] = "foo"; // okay
stuff["b"] = "bar"; // okay
stuff["c"] = false; // ERROR! bool != string
Есть ли способ заставить меня, чтобы значения были строками (или любым типом..)?
Ответы
Ответ 1
var stuff: { [s: string]: string; } = {};
stuff['a'] = ''; // ok
stuff['a'] = 4; // error
// ... or, if you're using this a lot and don't want to type so much ...
interface StringMap { [s: string]: string; }
var stuff2: StringMap = { };
// same as above
Ответ 2
Я ношу этот файл с собой, куда бы я ни пошел
export interface StringTMap<T> { [key: string]: T; };
export interface NumberTMap<T> { [key: number]: T; };
export interface StringAnyMap extends StringTMap<any> {};
export interface NumberAnyMap extends NumberTMap<any> {};
export interface StringStringMap extends StringTMap<string> {};
export interface NumberStringMap extends NumberTMap<string> {};
export interface StringNumberMap extends StringTMap<number> {};
export interface NumberNumberMap extends NumberTMap<number> {};
export interface StringBooleanMap extends StringTMap<boolean> {};
export interface NumberBooleanMap extends NumberTMap<boolean> {};
StringTMap
и NumberTMap
являются обобщенными и могут использоваться для создания карт любого типа (через let myTypeMap: StringTMap<MyType> = {}
). Остальные являются полезными предопределенными отображениями между общими литеральными типами.
Важным синтаксисом здесь является { [key: string]: T; }
, который обозначает, что интерфейс применяет литерал объекта с ключами типа string
(слово key
может быть любым идентификатором и должно использоваться для указания важности ключа) и значениями введите T
. Если вы хотите создать объект с string
"именами" для ключей и значениями boolean
(и не использовать наследование, описанное выше), его интерфейс будет { [name: string]: boolean }
.
Вы можете использовать аналогичный синтаксис, чтобы обеспечить наличие у объекта ключа для каждой записи в типе объединения:
type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";
type ChoresMap = { [day in DayOfTheWeek]: string };
const chores: ChoresMap = { // ERROR! Property 'saturday' is missing in type '...'
"sunday": "do the dishes",
"monday": "walk the dog",
"tuesday": "water the plants",
"wednesday": "take out the trash",
"thursday": "clean your room",
"friday": "mow the lawn",
};
Конечно, вы также можете сделать это универсальным типом!
type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";
type DayOfTheWeekMap<T> = { [day in DayOfTheWeek]: T };
const chores: DayOfTheWeekMap<string> = {
"sunday": "do the dishes",
"monday": "walk the dog",
"tuesday": "water the plants",
"wednesday": "take out the trash",
"thursday": "clean your room",
"friday": "mow the lawn",
"saturday": "relax",
};
const workDays: DayOfTheWeekMap<boolean> = {
"sunday": false,
"monday": true,
"tuesday": true,
"wednesday": true,
"thursday": true,
"friday": true,
"saturday": false,
}
10.10.2018 обновление:
Посмотрите ответ @dracstaxi ниже - теперь есть встроенный тип Record
, который делает это за вас.
Ответ 3
Быстрое обновление: начиная с Typescript 2.1 существует встроенный тип Record<T, K>
, который действует как словарь.
Пример из документации:
// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>
const names = { foo: "hello", bar: "world", baz: "bye" };
const lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number }
Документация по TypeScript 2.1 в Record<T, K>
Единственный недостаток, который я вижу при использовании этого над {[key: T]:K
, состоит в том, что вы можете закодировать полезную информацию о том, какой тип ключа вы используете вместо "ключа", например. если бы у вашего объекта были только простые ключи, вы могли бы намекнуть на это так: {[prime: number]: yourType}
.
Вот регулярное выражение, которое я написал, чтобы помочь с этими преобразованиями. Это преобразует только те случаи, когда метка является "ключом". Чтобы преобразовать другие метки, просто измените первую группу захвата:
Найти: \{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}
Заменить: Record<$2, $3>
Ответ 4
@Ответ Ryan Cavanaugh полностью в порядке и по-прежнему действителен. Тем не менее стоит добавить, что с Fall'16, когда мы можем утверждать, что ES6 поддерживается большинством платформ, почти всегда лучше придерживаться Карты, когда вам нужно связать некоторые данные с некоторым ключом.
Когда мы пишем let a: { [s: string]: string; }
, нам нужно помнить, что после typescript скомпилированного там нет такой вещи, как данные типа, она используется только для компиляции. И {[s: string]: string; } будет компилироваться только {}.
Тем не менее, даже если вы напишете что-то вроде:
class TrickyKey {}
let dict: {[key:TrickyKey]: string} = {}
Это просто не скомпилируется (даже для target es6
вы получите error TS1023: An index signature parameter type must be 'string' or 'number'.
Таким образом, практически вы ограничены строкой или номером в качестве потенциального ключа, поэтому здесь не так много ощущения принудительной проверки типа здесь, особенно учитывая, что когда js пытается получить доступ к ключу по номеру, он преобразует его в строку.
Таким образом, совершенно безопасно предположить, что наилучшей практикой является использование карты, даже если клавиши являются строками, поэтому я бы придерживался:
let staff: Map<string, string> = new Map();
Ответ 5
Основываясь на ответе @shabunc, это позволит принудительно использовать либо ключ, либо значение - или и то, и другое - что бы вы ни требовали.
type IdentifierKeys = 'my.valid.key.1' | 'my.valid.key.2';
type IdentifierValues = 'my.valid.value.1' | 'my.valid.value.2';
let stuff = new Map<IdentifierKeys, IdentifierValues>();
Также следует использовать enum
вместо определения type
.
Ответ 6
Определить интерфейс
interface Settings {
lang: 'en' | 'da';
welcome: boolean;
}
Использовать ключ, чтобы быть определенным ключом интерфейса настроек
private setSettings(key: keyof Settings, value: any) {
// Update settings key
}