Think of the following line of code in Typescript:
let x: 'a' | 'b' extends 'a' ? true : false;
I was wondering that the type of x
would be true since intuitively 'a' | 'b'
is an extended version of 'a'
(at least my intuition says so). I thought extends would work like subsets in math. A extends B
iff B ⊆ A
.
However, it seems that the actual type x
is false
here. I guess I don't understand how exactly the extends
keyword works.
CodePudding user response:
Extends checks type of the instance, doesn't checks the possible variables. Even if it works it would make no sense for complicated types like classes. If you check that with String it would be true.
let x: ('a' | 'b') extends String ? true : false;
Output would be;
true
You can take look at that documentation about Conditional Types from https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
CodePudding user response:
Per the Liskov Subsitution Principle, if Y
extends X
, then a value conforming to type Y
can be used wherever an X
is requested. This leads to a counterintuitive statement: when referring to the universe of possible values, Y
is more constrained than X
: All Y
s are X
, but not all X
s are Y
s.
In your example, because 'a' | 'b'
could be either 'a'
or 'b'
, that value doesn't extend type 'a'
, because 'b'
wouldn't substitute for 'a'
. Instead, 'a' extends ('a' | 'b')
, because all values that match 'a'
would work for 'a' | 'b'
.
As such, A extends B
iff A ⊆ B
.
One reason this is less intuitive in TypeScript is that we're talking about literal values here. It might make more sense for us to think of this in terms of objects, where {foo: number, bar: number}
extends {foo: number}
. The latter, {foo: number}
, could have a bar
property of any type or no bar
at all. The former {foo: number, bar: number}
is more specific and more constrained: not only is foo
a number, but bar
is also a number.
This is also why never
is assignable to everything: never
is the most constrained type because no actual values match it, so never
extends everything. The empty set is a subset of every set, so the empty type never
is the subtype of every type and can be assigned to every other type.
type Foo = { foo: number };
type Bar = never extends Foo ? true : false; // true