Home > front end >  What does this return type with never do vs just using an Interface?
What does this return type with never do vs just using an Interface?

Time:10-24

In trying to create a factory in Typescript I came across this article and I am confused by this part :

  class UserFactory {
  static getUser(k: Keys) : ExtractInstanceType<userTypes> {
    return new userMap[k]();
  }

The type definition for reference:

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

I am not sure why we need ExtractInstanceType & why we don't want to just use IStaff:

  class UserFactory {
  static getUser(k: Keys) : IStaff {
    return new userMap[k]();
  }

I am also confused as to what Keys is saying here, I thought it was just one of the keys in the dictionary that userMap can hold, which would be a string?

But when that method is used, is it is used with an object instance as it's parameter or is that not possible?

const manager = new Manager();
const anotherManger = UserFactory.getUser(manager);

CodePudding user response:

I am not sure why we need ExtractInstanceType & why we don't want to just use IStaff?

Because the whole point of the userMap and the associated static method is to map names to specific, concrete classes, not the interface they implement. If any of those classes implement class-specific stuff on top of the general IStaff interface (and they almost certainly will) your code would be littered with instanceof or other checks every time you called a method not in IStaff if you didn't nip it in the bud by narrowing the type with that static method to satisfy the compiler.

I thought it was just one of the keys in the dictionary that userMap can hold, which would be a string?

As I suggested in the comments on my answer to your other question I think the problem is that you are still conflating types and values. TBF it doesn't help that there's some overlap.

Consider the string 'a'. In Typescript, there is the value 'a' but there is also the literal type 'a', which is a subset of type string:

type a = 'a'
let a: a = 'a'
let s: string = a // note, variable a not string literal a!
const foo = {
  a: 1
}

console.log(foo[a]) // 1
console.log(foo[s]) // type error!

// ok, let's try the string literal 'a'
s = 'a'
console.log(foo[s]) // same type error!
// eff

Since a is literally 'a' we can index foo with it. But s is a mutable binding to a string, and you can't index foo with any old string even though we set the value to 'a', and even though we set it to the variable a which has type a, s is still a string the compiler has no way to guarantee that we don't re-assign a non-a value. playground

Types may look like values, and in certain contexts might have the exact same appearance as values, but they are not values, nor are values types. Moving right along...

For an object literal we know at compile-time the keys in the object, so if we have a foo object {a: 'hi', b: 5} then keyof typeof foo is 'a' | 'b'. N.B. those are strings, but in this case they're types, NOT Javascript values. The method in the blog post constrains the values that the k parameter can be passed to be only values in the compile-time known keys of the userMap.

But when that method is used, is it is used with an object instance as it's parameter or is that not possible?

Oh, it's possible to alter the method to work that way, it's just kinda pointless. If you have a reference to an instance, you already have a reference to its constructor you don't need a lookup table to make another one:

class Foo {}

const foo = new Foo()
const bar = new (foo.constructor as any)()
bar instanceof Foo // true

Playground

Again, the point of the userMap and that static method that accesses it is for the factory to be able to look up the correct user class by string name.

  • Related