【问题标题】:Object index key type in TypescriptTypescript中的对象索引键类型
【发布时间】:2017-06-11 17:51:44
【问题描述】:

我将我的泛型类型定义为

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

以上都不起作用。

那怎么办?

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    您只需使用 IDictionary&lt;TValue&gt; { [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 没有抱怨我为钥匙提供了一个数字。它正在工作。
    【解决方案2】:

    尽管对象键在底层始终是字符串,并且将索引器键入为字符串涵盖了数字,但有时您希望函数知道传递给它的对象的键。考虑这个映射函数,它与 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.
    

    如果不先将键转换为数字,我们就无法对键执行任何算术运算(请记住:"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] 将被限制为属性值类型。

    【讨论】:

      【解决方案3】:

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

      在这里您将有两个不同的条目。

      【讨论】:

      • 我不知道这是真的。果然Object.keys({ 1: "a", 2: "b", 3: "c" }).map(key =&gt; typeof key)返回["string", "string", "string"]。整洁!
      猜你喜欢
      • 1970-01-01
      • 2021-07-20
      • 2021-02-22
      • 2012-10-30
      • 2019-11-23
      • 2018-04-01
      • 2019-05-08
      • 1970-01-01
      相关资源
      最近更新 更多