Everything is functioning as expected. TypeScript utilizes structural subtyping, where a value of type X
can be assigned to a variable of type Y
if every required member in type Y
is present in type X
.
In order for a value to meet the criteria of being a valid MyInterface
, it must simply possess a myProp1
property of type string
. Since every instance of the class MyClass
includes this property, TypeScript allows you to assign a MyClass
instance to a variable of type MyInterface
without any errors.
It's worth noting that while TypeScript may highlight extra properties through excess property checking, warnings only occur in specific situations, typically when an object literal contains more properties than anticipated. As the expression new MyClass('1')
isn't an object literal, no warning is triggered, as documented in microsoft/TypeScript#5303.
Furthermore, the compiler doesn't monitor assignments made to variables of non-union types. The type of instance
is exclusively the non-union type
MyInterface</code, so the assignment <code>instance = new MyClass('1')
does not alter this. Consequently, there is no narrowing down to
MyClass
, meaning you cannot access the
myProp2
property via
instance
since
MyInterface
lacks knowledge of such a property.
This contrasts the behavior with union-typed variables whereby variables are narrowed after assignments to compatible union members:
let instance: MyClass | Date = new MyClass('1');
instance.myProp2; // permissible
This implies that it's preferable not to specify a variable's type with a non-union type unless intending for the variable to accommodate various values of that type without necessitating tracking. If instance
will consistently represent an instance of MyClass
and that's all that matters, specifying it as follows suffices:
let instance: MyClass = new MyClass('1');
instance.myProp2; // permissible
Alternatively, refrain from providing any annotations and allow the compiler to determine its type as MyClass
:
let instance = new MyClass('1');
instance.myProp2; // permissible
As a general rule of thumb, opt to let the compiler infer types whenever possible, reserving manual annotations for scenarios requiring greater control than inference provides.
Link to code on Playground