是否可以在Typescript中为接口定义动态类型?

时间:2020-07-21 04:07:54

标签: typescript

请考虑以下课程:

0: {Subject: "Maths", Grade: "11", Type: "Theory", MaxPrice: 1000}
1: {Subject: "Maths", Grade: "12", Type: "Theory", MaxPrice: 2000}
2: {Subject: "Maths", Grade: "13", Type: "Theory", MaxPrice: 3000}
3: {Subject: "Science", Grade: "10", Type: "Theory", MaxPrice: 4000}
length: 4
__proto__: Array(0)

此处的目标是允许对任何类型的对象建立索引,只要它们的export class GenericIndex<T> { private indexableAttribute: keyof T public constructor(indexableAttribute: keyof T) { this.indexableAttribute = indexableAttribute } public addToIndex(someObject: T): void { const indexValue: string = someObject[this.indexableAttribute] } } 类型为indexableAttribute。是否有一种方法可以指定接口string可以是具有T值且属性为indexableAttribute的任何对象?

当前,代码抛出一个

string

2 个答案:

答案 0 :(得分:2)

此类唯一关心的属性是可索引的属性。因此T可以扩展仅具有此一个属性的类型。它可能具有其他属性,并且通用参数会记住这些属性,但是您可以将此对象视为仅具有一个字符串键。而且由于打字稿需要知道该键的名称,所以它也必须是通用的。

因此,我们有一个字符串K,可以是任何字符串,但是对象类型必须在属性K处具有字符串才能通过类型检查。

所有这些都意味着这样的作品:

export class GenericIndex<T extends { [key in K]: string }, K extends string> {
    private indexableAttribute: K

    public constructor(indexableAttribute: K) {
        this.indexableAttribute = indexableAttribute
    }

    public addToIndex(someObject: T): void {
        const indexValue: string = someObject[this.indexableAttribute]
    }
}

// Good:
new GenericIndex<{ a: string, b: number }, 'a'>('a')

// Type '{ a: string; b: number; }' does not satisfy the constraint '{ b: string; }'.
new GenericIndex<{ a: string, b: number }, 'b'>('c')

Playground

现在const indexValue: string = someObject[this.indexableAttribute]之所以有效,是因为我们只知道T有一个密钥K,它是一个字符串。因此T[K]必须是字符串。


但是正如您所看到的,这里有一个缺点。使用泛型,要么推断所有参数,要么全部显式。您不能显式地推断出另一个。

因此,无法推断可索引属性K,因为无法推断T。并且T不能被推断,因为它不是构造函数的一部分。

因此您可以使用以下构造函数来解决此问题:

public constructor(indexableAttribute: K, objects?: T[]) {
    this.indexableAttribute = indexableAttribute

    if (objects) {
        for (const object of objects) {
            this.addToIndex(object)
        }
    }
}

现在可以让您执行以下操作:

// Inferred:
new GenericIndex('a', [{ a: 'abc', b: 123 }])
new GenericIndex('a', [] as { a: string, b: number }[] )

// Or omit the second argument and be the same as above.
new GenericIndex<{ a: string, b: number }, 'a'>('a')

Playground

答案 1 :(得分:1)

以下可能是您所需要的。

// The trick is to use this utility type
type SubType<Base, Condition> = Pick<Base, {
    [Key in keyof Base]: Base[Key] extends Condition ? Key : never
}[keyof Base]>;

export class GenericIndex<T> {
    private indexableAttribute: keyof SubType<T, string>;

    public constructor(indexableAttribute: keyof SubType<T, string>) {
        this.indexableAttribute = indexableAttribute;
    }

    public addToIndex(someObject: T): void {
        // Unfortunately I still have to convert the value to `any` here,
        // but our utility type can guard against misusage of our constructor
        const indexValue: string = someObject[this.indexableAttribute] as any;
    }
}

// Let's give it a try...

interface Test {
    a: string;
    b: number;
}

new GenericIndex<Test>("a"); // OK
new GenericIndex<Test>("b"); // Not OK

查看此Playground Link