Тип ключа индекса объекта в Typescript

Я определил свой общий тип как

interface IDictionary<TValue> {
    [key: string|number]: TValue;
}

Но TSLint жалуется. Как я должен определить тип индекса объекта, который может иметь любой ключ в качестве ключа? Я тоже пробовал их, но безуспешно.

interface IDictionary<TKey, TValue> {
    [key: TKey]: TValue;
}

interface IDictionary<TKey extends string|number, TValue> {
    [key: TKey]: TValue;
}

type IndexKey = string | number;

interface IDictionary<TValue> {
    [key: IndexKey]: TValue;
}

interface IDictionary<TKey extends IndexKey, TValue> {
    [key: TKey]: TValue;
}

Ничего из вышеперечисленного не работает.

Так как же тогда?


person Robert Koritnik    schedule 26.01.2017    source источник


Ответы (3)


Вы можете добиться этого, просто используя IDictionary<TValue> { [key: string]: TValue }, поскольку числовые значения будут автоматически преобразованы в строку.

Вот пример использования:

interface IDictionary<TValue> {
    [id: string]: TValue;
}

class Test {
    private dictionary: IDictionary<string>;

    constructor() {
       this.dictionary = {}
       this.dictionary[9] = "numeric-index";
       this.dictionary["10"] = "string-index"

       console.log(this.dictionary["9"], this.dictionary[10]);
    }
}
// result => "numeric-index string-index"

Как видите, строковые и числовые индексы взаимозаменяемы.

person Nickeat    schedule 26.01.2017
comment
Да, я тоже это сделал. Я был удивлен, что TSLint не жаловался на то, что я предоставил номер для ключа. И это работает. - person Robert Koritnik; 26.01.2017

В javascript ключи объекта могут быть только строками (а также символами es6).
Если вы передадите число, оно преобразуется в строку:

let o = {};
o[3] = "three";
console.log(Object.keys(o)); // ["3"]

Как видите, всегда получается { [key: string]: TValue; }.

Typescript позволяет вам определять такую ​​карту с numbers в качестве ключей:

type Dict = { [key: number]: string };

И компилятор проверит, что при присвоении значений вы всегда передаете число в качестве ключа, но во время выполнения ключи в объекте будут строками.

Таким образом, у вас может быть либо { [key: number]: string }, либо { [key: string]: string }, но не объединение string | number по следующим причинам:

let d = {} as IDictionary<string>;
d[3] = "1st three";
d["3"] = "2nd three";

Вы могли ожидать, что d будет иметь здесь две разные записи, но на самом деле там всего одна.

Что вы можете сделать, так это использовать Map:

let m = new Map<number|string, string>();
m.set(3, "1st three");
m.set("3", "2nd three");

Здесь у вас будет две разные записи.

person Nitzan Tomer    schedule 26.01.2017
comment
Я понятия не имел, что это правда. Конечно, Object.keys({ 1: "a", 2: "b", 3: "c" }).map(key => typeof key) возвращает ["string", "string", "string"]. Аккуратный! - person Sandy Gifford; 05.02.2018

Несмотря на то, что ключи объектов всегда являются строками под капотом, а ввод индексаторов в виде строк охватывает числа, иногда вам нужно, чтобы функция знала о ключах объектов, которые ей передаются. Рассмотрим эту функцию сопоставления, которая работает как Array.map, но с объектами:

function map<T>(obj: Object, callback: (key: string, value: any) => T): T[] {
    // ...
}

key может быть только string, а значение полностью нетипизировано. Наверное, хорошо в 9 случаях из 10, но мы можем добиться большего. Допустим, мы хотели сделать что-то вроде этой глупости:

const obj: {[key: number]: string} = { 1: "hello", 2: "world", 3: "foo", 4: "bar" };
map(obj, (key, value) => `${key / 2} ${value}`);
// error: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.

Мы не можем выполнять какие-либо арифметические операции с ключом без предварительного преобразования его в число (помните: "3" / 2 допустимо в JS и разрешается в number). Мы можем обойти это с помощью небольшого количества сложного набора текста в нашей функции карты:

function map<S, T>(obj: S, callback: (key: keyof S, value: S[keyof S]) => T): T[] {
    return Object.keys(obj).map(key => callback(key as any, (obj as any)[key]));
}

Здесь мы используем общий S для ввода нашего объекта и поиска типов ключей и значений непосредственно из него. Если ваш объект типизирован с использованием общих индексаторов и значений, keyof S и S[keyof S] будут преобразованы в постоянные типы. Если вы передадите объект с явными свойствами, keyof S будет ограничен именами свойств, а S[keyof S] будет ограничен типами значений свойств.

person Sandy Gifford    schedule 05.02.2018