What are types in TypeScript? This chapter describes two perspectives that help with understanding them.
The following two questions are important for understanding how types work and need to be answered from each of the two perspectives.
What does it mean for myVariable
to have the type MyType
?
How is UnionType
derived from Type1
, Type2
, and Type3
?
From this perspective, a type is a set of values:
If myVariable
has the type MyType
, then all values that can be assigned to myVariable
must be elements of the set MyType
.
The union type of the types Type1
, Type2
, and Type3
is the set-theoretic union of the sets that define them.
From this perspective, we are not concerned with values and how they flow when code is executed. Instead, we take a more static view:
The source code has locations and each location has a static type. In a TypeScript-aware editor, we can see the static type of a location if we hover above it with the cursor.
If a location whose type is Src
is assigned to a location whose type is Trg
, TypeScript determines if the assignment is legal via the type relationship assignment compatibility. Rules include:
Src
is assignable to Trg
if Src
and Trg
are identical types.Src
is assignable to Trg
if Src
or Trg
is the type any
.Src
is assignable to Trg
if Src
is a string literal type and Trg
is the primitive type string
.Src
is assignable to Trg
if Src
is a union type and each constituent type of Src
is assignable to Trg
.Src
is assignable to Trg
if Trg
is a union type and Src
is assignable to at least one constituent type of Trg
.Let’s consider the questions:
If myVariable
has the type MyType
, then we can only assign values to it whose static types are assignment-compatible with MyType
.
How union types work is also determined via assignment compatibility. We have seen two relevant rules.
An interesting trait of TypeScript’s type system is that the same variable can have different static types at different locations:
// %inferred-type: any[]
const arr = [];
arr.push(123);
// %inferred-type: number[]
arr;
arr.push('abc');
// %inferred-type: (string | number)[]
arr;
One of the responsibilities of a static type system is to determine if two static types are compatible:
Src
of an actual parameter (e.g., provided via a function call)Trg
of the corresponding formal parameter (e.g., specified as part of a function definition)This often means checking if Src
is a subtype of Trg
. Two approaches for this check are (roughly):
Sub
is a subtype of another type Sup
if Sub
has all parts of Sup
(and possibly others) and each part of Sub
has a subtype of the corresponding part of Sup
.
The following code produces a type error in line A with nominal type systems, but is legal with TypeScript’s structural type system because class A
and class B
have the same structure:
TypeScript’s interfaces also work structurally – they don’t have to be implemented in order to match: