Consider the following Typescript snippet:
class Animal {
constructor(name: string) {
this.name = name;
}
name: string;
haveBaby(name: string): ?? return type ?? {
return new this.constructor(name); // Error
}
}
class Cat extends Animal {}
class Dog extends Animal {}
class Gerbil extends Animal {} // etc.
let sorachi = new Cat("Sorachi"); // a: Cat
let sorachiJr = a.haveBaby("Sorachi Jr."); // I want: sorachiJr: Cat
Animals can have babies, and a baby should be the same kind of animal as the parent, i.e., should be an instance of the same class as the parent. How do I assign types in this situation, so that Typescript knows that sorachiJr: Cat
?
The code snippet above doesn't work. The line return new this.constructor(name)
produces the error [ts] Cannot use 'new' with an expression whose type lacks a call or construct signature.
in VS Code. The only solution I was able to find and understand was replacing this.constructor(name)
with (<any>this.constructor)(name)
or (<any>this).constructor(name)
, but then the type inferred for sorachiJr
is any
, too, rather than Cat
. I tried casting to typeof this
rather than any
, but got the error [ts] Cannot find name 'this'
.
How can I convince Typescript that having babies is a species-preserving operation?
Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters. Function return types are determined.
The TypeScript compiler infers the type information when there is no explicit information available in the form of type annotations. In TypeScript, TypeScript compiler infers the type information when: Variables and members are initialized. Setting default values for parameters. Determined function return types.
In addition to hexadecimal and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.
When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default to any . You usually want to avoid this, though, because any isn't type-checked. Use the compiler flag noImplicitAny to flag any implicit any as an error.
The preserving the type of the class the method was called on is easy, we just use the polymorphic this
type. To convince the ts that constructor
will be a constructor that takes a string
and returns an instance the same type as the current class requires a type assertion
type AnimalConstructor<T extends Animal> = new (name: string) => T
class Animal {
constructor(name: string) {
this.name = name;
}
name: string;
haveBaby(name: string): this {
return new (this.constructor as AnimalConstructor<this>)(name);
}
}
class Cat extends Animal {}
class Dog extends Animal {}
class Gerbil extends Animal {} // etc.
let sorachi = new Cat("Sorachi"); // a: Cat
let sorachiJr = sorachi.haveBaby("Sorachi Jr.");
Note Typescript can't validate the fact that the derived class constructor only expects a single string
parameter, it might require more or less parameters. This makes this constructor not type safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With