It is commonly believed that a derived class in OOP languages should be a superset of its base class, meaning it adds more things rather than removing them.
In most cases, type-casting a derived class instance to a base class works correctly only if this principle is followed.
However, TypeScript seems to deviate from this rule. In TypeScript, the types of properties in a base class can only become narrower in the derived class, which is quite unusual.
The following code snippet is allowed in TypeScript, but it may lead to issues:
class BaseClass1 {
value!: number | string;
}
class DerivedClass1 extends BaseClass1 {
moreValue!: string; // adding more properties is permitted
declare value: number; // narrowing the type
}
let baseInstance: BaseClass1 = new DerivedClass1();
baseInstance.value = "hello"; // though TypeScript allows it, it may not be correct
While the code below might seem fine, TypeScript signals that there's an issue:
class BaseClass2 {
value!: number;
}
class DerivedClass2 extends BaseClass2 {
declare value: number | string; // incorrect - cannot widen the type
}
Does this mean I have misunderstood OOP principles? Or if my understanding is correct and TypeScript indeed violates these rules, why did it choose this approach?
If a base class property's type becomes wider in a derived class, but the methods in the base class only handle the narrower type, calling a base class method in the derived class could result in problems. However, TypeScript finds itself in a dilemma regarding this matter.