Помогите с дженериками на TypeScript

Необходимо создать на TypeScript функцию с неявным типом (думаю смотреть в сторону дженериков). Функция принимает параметр-тип, и внутри этот тип будет использоваться как строка и как тип (нужно будет приводить результат функции к этому типу). Если на Delphi, то что-то вроде этого:

function func<T>(): T;
begin
    exit (T(obj.func(TClass(T).className)));

    // Функция "obj.func" принимает параметром строку с
    // именем типа, берет соответствующий ему объект из
    // массива (хэш-таблица) и возвращает этот объект
end;

То есть, созданные объекты хранятся в некоем массиве со строковыми ключами в другом объекте, и по имени класса мы получаем этот объект, а после получения должны привести этот объект к указанному классу (в моем примере это T).

Как реализовать такое на TypeScript?


Упрощенный пример:

//Родительский класс для объектов
class MyClass_Parent { ... }


//Объект-хранилище
class Storage {
    private _objects: {[key: string]: MyClass_Parent};
    
    public getObject(name: string): MyClass_Parent {
        return this._objects[name];
    }
}

//Внешний объект
class Outer {
    private _storage: Storage;

    ...

    public getMyClass<T>(name: string): T {
        ...
    }
}

Класс MyClass_Parent- родитель для дальнейших классов, на которых будут основаны объекты, помещаемые в Storage. Функция getMyClass<T> должна возвращать объект того класса, который указан на месте T.

Например, если в Storage хранится объект

'MyClass_Child': [object]

Тогда при вызове функции getMyClass<MyClass_Child> мы по ключу MyClass_Child получим из Storage объект, приведенный к указанному классу.


Ответы (2 шт):

Автор решения: Qwertiy

В принципе, правильнее было бы сформировать словарь руками, но если хочется больше автоматики, то достаточно иметь экспорт всех классов из одного места - по сути экспортируемая сущность представляет собой требуемый словарь и тайпскрипт об этом знает.

https://codesandbox.io/s/classes-dict-ti5py

classes/ClassA.ts

export class ClassA {
  doSmth() {
    return 42;
  }
}

classes/ClassB.ts

export class ClassB {
  doOther() {
    return "Here";
  }
}

classes/OtherClass.ts

export class OtherClass {
  oneMoreThing() {
    return -1;
  }
}

classes/index.ts

export * from "./ClassA";
export * from "./ClassB";
export * from "./OtherClass";

storage.ts

import * as dict from "./classes"

export class Provider {
  private storage = Object.create(null)

  get<K extends keyof typeof dict>(key: K): InstanceType<(typeof dict)[K]> {
    return this.storage[key] ||= new dict[key]()
  }
}

main.ts

import { Provider } from "./storage";

const p = new Provider();

const a = p.get("ClassA");
a.doSmth();

const b = p.get("ClassB");
b.doOther();

const c = p.get("OtherClass");
c.oneMoreThing();
→ Ссылка
Автор решения: qwabra
type $ctor = new (...args: any[]) => any
type $storeSet<T = any> = Set<T>
type $storeMap<T = $ctor> = Map<T, $storeSet>


class Store {
    #storeMap: $storeMap = new Map
    add<T extends $ctor>(ctor: T, instance: InstanceType<T>) {
        const storeSet = this.getSet(ctor)
        storeSet.add(instance)
    }
    getSet<T extends $ctor>(ctor: T): $storeSet<InstanceType<T>> {
        let storeSet: $storeSet<InstanceType<T>>
        if (this.#storeMap.has(ctor)) {
            storeSet = this.#storeMap.get(ctor) as any
        } else {
            storeSet = new Set
            this.#storeMap.set(ctor, storeSet)
        }
        return storeSet
    }
}

try {
    class Test {
        variable: any
    }
    // -,-
    void function test() {
        // -,-
        const store = new Store()
        store.add(Test, new Test)
        // -,-
        const testSet = store.getSet(Test)
        // -,-
        const firstTestInstance = Array.from(testSet.values())[0]
        firstTestInstance.variable // (property) Test.variable: any
        console.log(firstTestInstance instanceof Test) // true
    }()
    // -,-
    void function test2() {
        // -,-
        const store = new Store()
        store.add(Test, new Test)
        // -,-
        const testSet = store.getSet((new Test).constructor as any) as $storeSet<Test>
        // -,-
        const firstTestInstance = Array.from(testSet.values())[0]
        firstTestInstance.variable // (property) Test.variable: any
        console.log(firstTestInstance instanceof Test) // true
    }()
} catch (e) { console.log(e) }
→ Ссылка