Помогите с дженериками на 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 шт):
В принципе, правильнее было бы сформировать словарь руками, но если хочется больше автоматики, то достаточно иметь экспорт всех классов из одного места - по сути экспортируемая сущность представляет собой требуемый словарь и тайпскрипт об этом знает.
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();
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) }