在Typescript中的对象索引键类型

85

我将我的通用类型定义为

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;
}

以上方案均不适用。

那么应该怎么做呢?

3个回答

93
你可以通过使用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"

正如您所看到的,字符串和数字索引是可以互换的。


是的,我也这样做了。我很惊讶TSLint没有抱怨我为键提供数字。而且它还有效运行。 - Robert Koritnik

49

在JavaScript中,对象的键只能是字符串(以及在es6中也可以是符号)。
如果您传递一个数字,它会被转换为字符串:

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

如您所见,您总是得到 { [key: string]: TValue; }

使用Typescript,您可以定义一个具有数字键的映射,如下所示:number

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

这里你将获得两个不同的条目。


1
我之前不知道这是真的。果然 Object.keys({ 1: "a", 2: "b", 3: "c" }).map(key => typeof key) 返回 ["string", "string", "string"]。很棒! - Sandy Gifford

1
尽管对象键在底层始终为字符串,并且将索引器类型定义为字符串可以涵盖数字,但有时您希望函数能够了解传递给它的对象的键。考虑这个映射函数,它类似于 Array.map 但针对对象:
function map<T>(obj: Object, callback: (key: string, value: any) => T): T[] {
    // ...
}

key 仅限于为 string 类型,而值则是完全无类型的。这在大多数情况下可能没问题,但我们可以做得更好。比如说,如果我们想要做一些傻事:

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)。我们可以通过在map函数中进行一些巧妙的类型转换来解决这个问题:

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]将被限制为属性值类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接