Вывести общий тип из ключа объекта - PullRequest
1 голос
/ 21 марта 2019

Мой вопрос немного сложнее, поэтому, возможно, все необходимые детали:

// 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, который реализует getter, и затем производные классы, которые передают свой тип базовому классу. Также существует тип 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 и bHandler были автоматически выведены как Foo и Goo.

1 Ответ

1 голос
/ 21 марта 2019

Вы можете использовать условные типы для извлечения типа хандера из Id.Я также немного изменил способ получения ключей, он не работал со строгими нулевыми проверками:

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