Я определяю Manager для управления набором модулей и хочу получить доступ к методу экземпляра Module
через экземпляр Manager
. Можно ли это предположить?
interface Module<T extends string> {
readonly moduleName: T;
}
class Manager<
T extends Module<any>[],
> {
api: any;
constructor(modules: T) {}
}
class AMoudle {
static moduleName = 'a'
aFun() {}
}
class BMoudle {
static moduleName = 'b'
bFun() {}
}
const manager = new Manager([AMoudle, BMoudle]);
manager.api.a.aFun()
Да мы можем! Сначала мы получаем массив имен модулей:
type GetModNames<Mods, Names extends string[] = []> = Mods extends readonly [Module<infer Name>, ...infer Rest] ? GetModNames<Rest, [...Names, Name]> : Names;
Это «перебирает» модули и выводит каждое из имен, а затем добавляет имя к результату. Наконец, когда это сделано, он «возвращает» имена, которые он вывел.
Это требует некоторых изменений заранее:
Имена модулей также должны быть readonly
:
class AMoudle {
// READONLY
static readonly moduleName = 'a'
aFun() {}
}
Класс Manager должен ожидать массивы только для чтения:
class Manager<
T extends ReadonlyArray<Module<string>>,
> {
И когда вы делаете менеджера, вы должны использовать as const
:
const manager = new Manager([AMoudle, BMoudle] as const);
Имея это в виду, вот как мы создадим тип свойства api
:
type MakeApi<Mods> = {
//@ts-ignore ¯\_(ツ)_/¯
[K in GetModNames<Mods>[number]]: InstanceType<Exclude<Mods[number], Exclude<Mods[number], { moduleName: K }>>>
};
Проходим по каждому из полученных названий модов и получаем модуль с такой интересной конструкцией:
Exclude<Mods[number], Exclude<Mods[number], { moduleName: K }>>
Затем, поскольку модуль является классом, нам нужен его экземпляр, поэтому мы заключаем его в InstanceType
.
К сожалению, я не знаю, как обойти эту ошибку, поэтому использовал ts-ignore
:(