user12694034
user12694034

Reputation:

TypeScript typeof and class constructor problem

I have a map like this:

  const map = {
    a: ClassA,
    b: ClassB,
    c: ClassC,
  }

which maps to different classes. and I have this function, which is returning instance of a class mapped by a key in map object:

const conf = {};

type VersionMap = typeof map;
type Versions = keyof VersionMap;

const getter = <T extends Versions>(ver: T): VersionMap[T] => new map[ver](conf);

But problem is that typeof is referencing to class constructor, not an instance so I got an error. How can I solve that issue without creating second type?

Upvotes: 1

Views: 529

Answers (1)

r3dst0rm
r3dst0rm

Reputation: 1926

I think there is some small misconception here on how classes are typed. Let's assume, for abbreviation here, that we have two classes ClassA and ClassB.

class ClassA {
  constructor() {
    console.log("Class A");
  }
}

class ClassB {
  constructor() {
    console.log("Class B");
  }
}

Creating the map object is easy, but I think, it would have been easier to first describe an interface on what this object is supposed to hold. But as in your example, I proceed without one.

const map = {
  a: ClassA,
  b: ClassB,
};

Now comes the tricky part - Typings. First I want to extract all keys of the map object. Therefore I'll create a new type called: Keys

// Type contains: "a" | "b"
type Keys = keyof typeof map;

Next I need all associated values for each key. To achieve this, create a new type at the index of Keys.

// Type contains: typeof ClassA | typeof ClassB
type Values = typeof map[Keys];

The getter function should return an instance of a class T. But the Values type contains all classes as typeof ClassA | typeof ClassB which is equal to new() => ClassA | new() => ClassB. Thus the type of ClassA | ClassB is not compatible.

So let's craft a seperate type to get rid of this. The type should return the instance type of a class - just ClassA or ClassB. Or so to say, the return type of new SomeClass() operation.

type ReturnType<T> = T extends new() => infer R ? R : never;

If you want to read up on conditional types in TypeScript, I highly recommend reading this blog post: https://mariusschulz.com/blog/conditional-types-in-typescript Let's add the final piece:

const conf = {};
const getter = (ver: Keys): ReturnType<Values> => new map[ver]();

FYI: I did change the naming of VersionMap to Values and Version to Keys as I think it's easier to understand what each of those types is supposed to do. But feel free to change them to your needs.

Upvotes: 2

Related Questions