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