What are types in TypeScript? This chapter describes two perspectives that help with understanding them. Both are useful; they complement each other.
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 arg
to have the type MyType
?
function myFunc(arg: MyType): void {}
How is UnionType
derived from Type1
and Type2
?
type UnionType = Type1 | Type2;
From this perspective, we are interested in values and a type is a set of values:
myFunc()
if it is included in MyType
.
UnionType
(a set) is defined as the set-theoretic union of Type1
and Type2
.
From this perspective:
The most important type relationship is assignment compatibility: Can a location whose type is Src
be assigned to a location whose type is Trg
? The answer is yes if:
Src
and Trg
are identical types.
Src
or Trg
is the type any
.
Src
is a string literal type and Trg
is the primitive type string
.
Src
is a union type and each constituent type of Src
is assignable to Trg
.
Src
is an intersection type and at least one constituent type of Src
is assignable to Trg
.
Trg
is a union type and Src
is assignable to at least one constituent type of Trg
.
Trg
is an intersection type and Src
is assignable to each constituent type of Trg
.
Let’s consider the questions:
arg
having type MyType
means that we can only pass a value to myFunc()
whose type is assignable to MyType
.
UnionType
is defined by the relationships it has with other types. Above, we have seen two rules for union types.
One of the responsibilities of a static type system is to determine if two static types are compatible – e.g.:
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)
The type system needs to check if Src
is assignable to Trg
. Two approaches for this check are (roughly):
In a nominal or nominative type system, two static types are equal if they have the same identity (“name”). Src
is only assignable to Trg
if they are equal or if a relationship between them was specified explicitly – e.g., an inheritance relationship (extends
).
In a structural type system, a type Src
is assignable to a type Trg
if Trg
has a structure that can receive what’s in Src
— e.g.: For each field Src.F
, there must be a field Trg.F
such that Src.F
is assignable to Trg.F
.
The following code produces a type error in the last line with a nominal type system, but is legal with TypeScript’s structural type system because class A
and class B
have the same structure:
class A {
typeName = 'A';
}
class B {
typeName = 'B';
}
const someVariable: A = new B();
TypeScript’s interfaces also work structurally – they don’t have to be implemented in order to match:
interface HasTypeName {
typeName: string;
}
const hasTypeName: HasTypeName = new A(); // OK
TypeScript Language Specification 1.8: TypeScript originally had a formal language specification but it was discontinued after TypeScript 1.8 (which came out in 2016). It has since been removed from TypeScript’s repositories, but a PDF file can still be downloaded from an old commit. Especially helpful: section “3.11 Type Relationships”.