Typescript 中的对象索引键类型

Object index key type in 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;
}

None 以上作品。

那怎么办?

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

这里会有两个不同的条目。

您只需使用 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"

如您所见,字符串和数字索引可以互换。

尽管对象键始终是引擎盖下的字符串,并且将索引器键入为字符串覆盖数字,但有时您希望函数知道传递给它的对象的键。考虑这个映射函数,它的工作方式类似于 Array.map 但对象为:

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

key 被限制为 string,并且值完全没有类型。可能 10 次中有 9 次都很好,但我们可以做得更好。假设我们想做这样的傻事:

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.

我们不能对 key 执行任何算术运算,除非先将其转换为数字(请记住:"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 SS[keyof S] 将解析为常量类型。如果您传入具有明确属性的对象,keyof S 将被限制为 属性 名称,而 S[keyof S] 将被限制为 属性 值类型。