Home > Software engineering >  How to infer type from an Array of Interface
How to infer type from an Array of Interface

Time:03-21

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()

playground

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 :(

Playground

  • Related