简体   繁体   English

从对象的键推断泛型类型

[英]Infer generic type from key of object

My question is a bit more complex so here are possibly all the required parts:我的问题有点复杂,所以这里可能是所有必需的部分:

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

So what I have is an interface , then a base class that implements a getter , and then derived classes that pass their type to the base class.所以我拥有的是一个interface ,然后是一个实现getterbase类,然后是将它们的类型传递给基类的派生类。 Also there is an Id type the holds unique identifier of handler as well as it's type.还有一个Id类型,它包含处理程序的唯一标识符及其类型。 Each handler has it's own id in id variable and then can save references to other handlers in attribute of type Id with generic argument of that handler's type.每个处理程序在id变量中都有自己的 id,然后可以使用该处理程序类型的通用参数保存对Id类型属性中其他处理程序的引用。

What I would like to make work is to properly type the get function so it infers the type of handler it is getting by provided key .我想要做的工作是正确键入get函数,以便它通过提供的key推断它正在获取的处理程序的类型。 Since each key is templated by type of handler it is pointing to, this type should be probably used to infer the return type, but with setup like this it does not work.由于每个键都是由它指向的处理程序类型模板化的,因此该类型可能应该用于推断返回类型,但是像这样的设置它不起作用。

You can see an example of desired usage here:您可以在此处查看所需用法的示例:

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

What I would like to achieve here is for the attributes aHandler and bHandler to be automatically inferred as Foo and Goo .我想在这里实现的是将属性aHandlerbHandler自动推断为FooGoo

You can user conditional types to extract the hander type from Id .您可以使用条件类型从Id提取处理程序类型。 I also changed a bit the way you were getting the keys, it did not work with strict null checks :我也改变了你获取密钥的方式,它不适用于严格的空检查:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM