I'm not sure if my question is clear, so I'll try to elaborate.
Say I have a class MyClass
that implements an interface MyInterface
. Apart from the properties required by the implementation (in this case, myProp1
), it has an additional property myProp2
as shown in the snippet below:
interface MyInterface {
myProp1: string;
}
class MyClass implements MyInterface {
myProp2 = '2';
constructor(public myProp1: string) {}
}
Then, upon creating an instance of the class, I assigned it the MyInterface
type. TypeScript doesn't complain even though instance
has properties not specified in MyInterface
:
let instance: MyInterface = new MyClass('1'); // no type error
But when attempting to access the added property (myProp2
), I get an error:
console.log(instance.myProp2); // 'myProp2' does not exist on type 'MyInterface'. Did you mean 'myProp1'?
Is this a bug, or is it the intended behavior here?
CodePudding user response:
This is working as intended. TypeScript has structural subtyping, which means that a value of type X
is assignable to a variable of type Y
if every member required in type Y
is present in type X
.
So all that's needed for a value to be a valid MyInterface
is for it to have a myProp1
property of type string
. And every instance of MyClass
does have such a property (which is why implements MyInterface
compiles without error), so according to TypeScript, it's perfectly fine to assign a MyClass
instance to a variable of type MyInterface
.
It is true that TypeScript sometimes does complain about extra properties, via excess property checking, but this only triggers in certain circumstances; in particular if you assign an object literal with more properties than expected. Since the expressionnew MyClass('1')
is not an object literal, there is no warning. This is described as being intentional in microsoft/TypeScript#5303.
Furthermore, the compiler does not keep track of what's been assigned to a variable of a non-union type. The type of instance
is the non-union MyInterface
type, and so the assignment instance = new MyClass('1')
does not change that. It is not narrowed to MyClass
, and thus you cannot access the myProp2
property through instance
(as MyInterface
has no known myProp2
property).
Note that this differs from the behavior on union-typed variables, where assignments will narrow the variable to just those union members with which the assigned value is compatible:
let instance: MyClass | Date = new MyClass('1');
instance.myProp2; // okay
This means that you really don't want to annotate a variable's type with a non-union type unless you want that variable to take on different values of that type and you don't need to keep track of which value it has. If instance
will always be an instance of MyClass
and that's all you need to know, then you can annotate it as such:
let instance: MyClass = new MyClass('1');
instance.myProp2; // okay
or don't annotate it at all and let the compiler infer its type to be MyClass
:
let instance = new MyClass('1');
instance.myProp2; // okay
Generally speaking it's a good idea to let the compiler infer types for you and only annotate things if you need to exert more control than inference gives you.
CodePudding user response:
It is indeed the intended behaviour.