Home > Enterprise >  Can I infer a type in an abstract class?
Can I infer a type in an abstract class?

Time:09-22

I would like to see if the return type of a function in an abstract class can be overridden by a child class:

Parent.ts

abstract class Parent<T> {
  abstract data: T | T[];

  // Can this return as T or T[] (not T | T[]) depending on implementation of 
  // child?
  getData() {
    return this.data;
  }
}

Child.ts

class Child extends Parent<Person> {
  data: Person[] = [];
}

index.ts

const child = new Child();
child.getData() // Can this be returned as Person[] automatically without typecast?

I know I can typecast in index.ts, but I was wondering if there is a way for TypeScript to automatically infer the type of data.

CodePudding user response:

As @jcalz pointed out in the comments. The answer is to use polymorphic this types. Thanks for your help!

CodePudding user response:

When you want a class to dynamically refer to the type of the "current" class instance, which could be a more specific subclass, you can use the polymorphic this type (that is, you use this as a type).

In fact, if you inspect the type of this inside getData()'s implementation, you will see that the compiler infers it to be of type this:

// this: this

But the compiler will often widen this to the class type, especially when you access a property; so while the type of this is seen as type this, the type of this.data is seen as type Parent<T>['data'], which is T | T[]. Instead, you want this['data'], meaning "the type of the data property on whatever this is".

If you want to prevent that widening of this['data'] to Parent<T>['data'], you can explicitly annotate a new variable of this type:

getData() {
    const data: this['data'] = this.data;
    return data;
}

Or you can annotate the return type of the method:

getData(): this['data'] {
    return this.data;
}

Or you can assert that the return value is of that type:

getData() {
    return this.data as this['data'];
}

Once you do this, then callers of getData() in subclasses will have the more specific type information you care about:

interface Person { name: string }

class Child extends Parent<Person> {
    data: Person[] = [];
}

const child = new Child();
child.getData().map(person => person.name); // okay

Playground link to code

  • Related