从对象的键推断泛型类型

问题描述 投票:1回答:1

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

// 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,然后是一个实现basegetter类,然后派生类将它们的类型传递给基类。还有一个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");
    }
}

我想在这里实现的是aHandlerbHandler属性被自动推断为FooGoo

typescript type-inference typescript-generics
1个回答
1
投票

您可以使用条件类型从Id中提取hander类型。我也改变了你获取密钥的方式,它不能用于严格的空检查:

// 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");
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.