I define a Manager to manage a set of modules, and I want to access the method of the Module
instance through the Manager
instance. Can this be inferred?
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()
CodePudding user response:
Yes we can! First we get an array of module names:
type GetModNames<Mods, Names extends string[] = []> = Mods extends readonly [Module<infer Name>, ...infer Rest] ? GetModNames<Rest, [...Names, Name]> : Names;
This "loops" through the modules and infers each of the names, then adds the name to the result. Finally when it is done, it "returns" the names it inferred.
This requires some changes beforehand:
Module names have to be readonly
as well:
class AMoudle {
// READONLY
static readonly moduleName = 'a'
aFun() {}
}
The Manager class should expect readonly arrays:
class Manager<
T extends ReadonlyArray<Module<string>>,
> {
And when you make the manager you have to use as const
:
const manager = new Manager([AMoudle, BMoudle] as const);
With that in place, here's how we will make the api
property's type:
type MakeApi<Mods> = {
//@ts-ignore ¯\_(ツ)_/¯
[K in GetModNames<Mods>[number]]: InstanceType<Exclude<Mods[number], Exclude<Mods[number], { moduleName: K }>>>
};
We go through each of the mod names we got, and get the module with this interesting construction:
Exclude<Mods[number], Exclude<Mods[number], { moduleName: K }>>
Then because the module is a class we need an instance of it, so we wrap that in InstanceType
.
Unfortunately I don't know a method to get around the error, so I used ts-ignore
there :(