Подтвердить что ты не робот

Выполнение типа проиндексированных элементов объекта Typescript?

Я хотел бы сохранить сопоставление строки → string в объекте Typescript и обеспечить, чтобы все ключи отображались в строках. Например:

var stuff = {};
stuff["a"] = "foo";   // okay
stuff["b"] = "bar";   // okay
stuff["c"] = false;   // ERROR!  bool != string

Есть ли способ заставить меня, чтобы значения были строками (или любым типом..)?

4b9b3361

Ответ 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
}