从对象的键推断通用类型

时间:2019-03-21 16:21:22

标签: typescript type-inference typescript-generics

我的问题有点复杂,所以这里可能是所有必需的部分:

// Common interface with an id and a string lieral type
interface IHandler {
    id: Id<IHandler>;
    type: string;
}

// Base class for all the handler, with generic argument providing string literal type and access to inheriting type
class Base<Type extends string, T extends Base<Type, T>> implements IHandler {
    id: Id<T> = Guid.raw() as any

    // In consturctor string literal type is instantiated
    protected constructor(public type: Type) {
        this.type = type;
    }

    // Static function that retrieves handler by it's key from a store
    static GetHandler: <T extends IHandler>(get: Id<T>) => T;

    // Getter that accepts key of an id attribute and returns instance from store
    get = <Handler extends Base<Handler["type"], Handler>>(key: HandlerIds<T, Handler>) => Base.GetHandler<Handler>((this as any)[key]);
}

// Keys of attributes that are Id's
type HandlerIds<T extends Base<T["type"], T>, U extends Base<U["type"], U>> = keyof SubType<Model<T>, Id<U>>;

// Creates a subtype based on condition
type SubType<Base, Condition> = Pick<Base, { [Key in keyof Base]: Base[Key] extends Condition ? Key : never }[keyof Base]>;

// Excludes attributes of Base
type Model<T extends Base<T["type"], T>> = Partial<Pick<T, Exclude<keyof T, keyof Base<T["type"], T>>>>;

// Type that holds guid reference and also a type that guid is supposed to be pointing to
type Id<T extends IHandler> = string & { __type: T }

所以我拥有的是一个interface,然后是一个实现base的{​​{1}}类,然后是将其类型传递给基类的派生类。另外还有一种getter类型的内容,它包含处理程序的唯一标识符及其类型。每个处理程序在Id变量中都有自己的ID,然后可以使用该处理程序类型的通用参数将对其他处理程序的引用保存在类型为id的属性中。

我想做的是正确键入Id函数,以便它推断提供的get获取的处理程序类型。由于每个键都是根据它所指向的处理程序类型进行模板化的,因此应该使用该类型来推断返回类型,但是对于这样的设置,它是行不通的。

您可以在此处查看所需用法的示例:

key

我在这里想要实现的是将属性class Foo extends Base<"Foo", Foo> { a: Id<Goo>; b: Id<Foo>; constructor() { super("Foo"); // Get a reference to instance of "a" of type Goo var aHandler = this.get("a"); // Get a reference to instance of "b" of type Foo var bHandler = this.get("b"); } } class Goo extends Base<"Goo", Goo> { constructor() { super("Goo"); } } aHandler自动推断为bHandlerFoo

1 个答案:

答案 0 :(得分:1)

您可以使用条件类型从Id中提取处理程序类型。我也对您获取密钥的方式进行了一些更改,它不适用于严格的null检查:

// Common interface with an id and a string lieral type
interface IHandler {
    id: Id<IHandler>;
    type: string;
}

// Base class for all the handler, with generic argument providing string literal type and access to inheriting type
class Base<Type extends string, T extends Base<Type, T>> implements IHandler {
    id: Id<T> = Guid.raw() as any

    // In consturctor string literal type is instantiated
    protected constructor(public type: Type) {
        this.type = type;
    }

    // Static function that retrieves handler by it's key from a store
    static GetHandler: <T extends IHandler>(get: Id<T>) => T;

    // Getter that accepts key of an id attribute and returns instance from store
    get<K extends HandlerIds<T, Array<Id<any>>>>(key: K, index: Extract<keyof T[K], number>) : HandlerFromIdArray<T[K]> 
    get<K extends HandlerIds<T, Id<any>>>(key: K) : HandlerFromId<T[K]>
    get<K extends HandlerIds<T, Id<any>>>(key: K, index?: number) : IHandler { 
        return Base.GetHandler<IHandler>((this as any)[key]) as any; // this assertion is needed 
    }
}

// Keys of attributes that are Id's
type HandlerIds<T extends Base<T["type"], T>, C> = Exclude<{ [P in keyof T]-?: T[P] extends C ? P : never}[keyof T], keyof Base<string, any>>;

// Type that holds guid reference and also a type that guid is supposed to be pointing to
type Id<T extends IHandler> = string & { __type: T }
type HandlerFromId<T> = T extends Id<infer U> ? U: never;
type HandlerFromIdArray<T> = T extends Array<Id<infer U>> ? U: never;

class Foo extends Base<"Foo", Foo> {
    a!: Id<Goo>;
    b!: Id<Foo>;
    c!: Id<Foo>[];
    constructor() {
        super("Foo");
        // Get a reference to instance of "a" of type Goo
        var aHandler = this.get("a");
        // Get a reference to instance of "b" of type Foo
        var bHandler = this.get("b");

        var cHandler = this.get("c", 1); // Foo
    }
}
class Goo extends Base<"Goo", Goo> {
    constructor() {
        super("Goo");
    }
}