When it comes to TypeScript, it acts as a safety net against potential mistakes, although its approach may be puzzling at times.
To simplify, let's focus on the interfaces A
and B
.
interface A {
f1( ) : void;
}
interface B extends A {
f2( ) : void;
}
type expectA = ( a: A ) => void;
type expectB = ( b: B ) => void;
function testA( a: A ) {
a.f1();
}
function testB( b: B ) {
b.f1();
b.f2();
}
const v1: expectA = testA;
const v2: expectA = testB; // error: Property 'f2' is missing in type 'A' but required in type 'B'
const v3: expectB = testB;
const v4: expectB = testA;
Initially, seeing that only v2
has an error might seem counterintuitive. If B
extends A
, why can't we use B
everywhere A
can be used?
The reason lies in the nature of functions. Take a closer look at testB()
. It utilizes the property b.f2()
assuming that its parameter b
possesses that property. However, const v2: expectB
represents the type (a: A) => void
. An argument of type A
does not have f2()
, leading to conflicting information about the type of
v2</code and creating a paradoxical situation.</p>
<p>(Note that this discrepancy is unrelated to whether <code>testB
actually calls
b.f2()</code; the issue arises from the potentiality of such an action based on the argument types defined.)</p>
<p>Regarding <code>const v4
, although you consider it "abnormal", upon revisiting the functions, it becomes evident why it is acceptable. Passing either a variable of type
A
or
B
to
testA()
poses no risk since it never attempts to access the
f2()
property.
Furthermore, the usage of extends
in TypeScript differs from common expectations. Declaring interface B extends A
simply denotes that B
inherits all properties of A
, without implying a substitution relationship between instances of A
with instances of
B</code. For true polymorphic behavior, classes must be employed, like so: <code>class B extends A implements A
.
class A {
foo = '';
}
class B extends A implements A {
bar = '';
}
let isA = new A();
isA = new B(); // valid due to B implementing A
let isB = new B();
isB = new A(); // error: Property 'bar' is missing in type 'A' but required in type 'B'