如何将一般打字稿限制为索引器?

时间:2016-11-30 12:53:05

标签: generics typescript

在打字稿中,您只能使用stringnumbersymbol索引对象。

我想要一个允许将泛型参数用作索引的泛型。

function foo<T extends number|string>(a: T): void {
    let x: any = {};
    x[a] = 42; // Error: an index expression argument must be of type...
}

如果我投射到number|string,它会编译:

function foo<T extends number|string>(a: T): void {
    let x: any = {};
    let i: number|string = a;
    x[i] = 42; // OK
}

因此,类型检查器非常智能,知道如果T扩展number|string,那么我可以将其分配给number|string(然后我可以使用它来索引对象)但出于某种原因,它不会让我直接用T索引。

有没有办法在泛型约束中指定传递的类型可以用作索引器?

注意:我正在使用通用而不是number|string,因为我想限制通用只接受来自特定enum的值字符串文字联盟

编辑:这是一个更具说服力的例子(更接近我真正想要做的事情),是:

// The following line fails to compile even though Key is number | string
type Dictionary<Key extends number | string, Value> = { [index: Key]: Value }; 

// Had it compiled I would expect the following behaviour
type Answer = "yes" | "no" | "maybe";
let grades = {} as Dictionary<Answer, number>;  

grades["yes"] = 10;
grades["nope"] = 5; // Should be an error "nope" is not assignable to Answer

2 个答案:

答案 0 :(得分:1)

// The following line fails to compile even though Key is number | string
type Dictionary<Key extends number | string, Value> = { [index: Key]: Value };

使用in关键字:

type Dictionary<TKey extends number | string, TValue> = {
  [key in TKey]: TValue
};

答案 1 :(得分:0)

如果你实际上要传递扩展字符串或数字的类的实例,那么坚持使用通用方法,然后将其转换 问题是编译器无法知道传递值的类型(在运行时),所以它会警告你,通过强迫你告诉它它没关系,它应该信任你

如果您只想将参数的类型限制为特定的枚举或字符串文字,那么您可以这样做:

enum MyEnum { A, B, C };

function foo(a: MyEnum): void;
function foo(a: "A" | "B" | "C"): void;
function foo(a: any): void {
    let x: any = {};
    x[a] = 42;
}

修改

好的,您可以使用toString代替转换:

function foo<T extends number|string>(a: T): void {
    let x: any = {};
    x[a.toString()] = 42;
}

但如果你正在使用它,那么你几乎可以传递任何东西(只要它有toString方法)。
这很有效,因为对象的键总是javascript中的字符串,即使在执行:

时也是如此
let o = { 1: "one", 2: "two", 3: "three" };
Object.keys(o).forEach(key => console.log(typeof key)); // string (x3)

否则你需要施放。

第二次编辑

您有两种选择:

(1)演员:

class A<T extends number | string> {
    private x: any;
    private key: T;

    constructor(key: T) {
        this.x = {};
        this.key = key;
    }

    fn(value: any) {
        this.x[this.key as number | string] = value;
    }
}

code in playground

(2)toString:

class A<T extends number | string> {
    private x: any;
    private key: T;

    constructor(key: T) {
        this.x = {};
        this.key = key;
    }

    fn(value: any) {
        this.x[this.key.toString()] = value;
    }
}

code in playground

它们都是等价的,因为在将变量用作对象中的索引之前,javascript将调用toString本身。

您可能希望编译器只是让您这样做,但它只是试图保护您。
因为键总是字符串,使用toString然后编译器的强制执行似乎很奇怪,所有这些在javascript中工作:

let obj = {};
let date = new Date();
obj[date] = 3;

但会引发编译错误:

  

索引表达式参数的类型必须为&#39; string&#39;,&#39; number&#39;,   &#39;符号&#39;或&#39;任何&#39;

我想这是因为作为开发人员,如果它不会出错,那么您认为密钥是Date的一个实例,然后期望得到它在迭代键时返回(例如)。

通过引发错误,编译器会让您知道您正在做的事情并不像您预期​​的那样直截了当,但是通过投射您告诉它您已经意识到这一点

第3次编辑

你想要的是目前不可能的,但它将是打字稿2.1.3 本期内容更多内容:String literal types as index signature parameter types?

但是,如果这是您的方案,那么您只需使用Map object

type Answer = "yes" | "no" | "maybe";

let grades = new Map<Answer, number>();
grades.set("yes", 10);
grades.set("nope", 5); // error: Argument of type '"nope"' is not assignable to parameter of type 'Answer'

code in playground