Narrowing
The process of turning a general type into a more specific type is called narrowing (verengen / kleiner machen).
Consider this type declaration:
type StringOrArray = string | string[];
const input: StringOrArray =
Math.random() > 0.5 ? "string" : ["string", "array"];
We cannot really work with this type unless we determine the actual runtime type of our variable.
- if we determine that the type is
string
, we can use all the string methods that are available - if we determine that the type is
string[]
, we can use array methods likefilter
ormap
To determine the type of a variable at runtime, we can use if
statements and other type assertions.
if (typeof input == "string") {
// We can now use string methods
console.log(input.toUpperCase());
}
The if
statement ensures that the type of input is string
and TypeScript, therefore, narrows the type of input
from string | string[]
to string
.
TypeScript is also able to determine what the type is going to be in the else branch in this example:
if (typeof input == "string") {
console.log(input.toUpperCase());
} else {
// input is of type string[]
console.log(input.map((str) => str.toLowerCase()));
}
Because input can either be of type string
or string[]
, TypeScript can conclude that if input
is not a string
, then it has to be of type string[]
.
Any and Unknown
Narrowing also works on types like any
and unknown
.
function func(input: unknown) {
if (typeof input === "string") {
console.log("input is of type string");
}
}
Type guards
There are many different ways to perform these runtime checks (also called type guards
).
Primitive types
function func(input: boolean | number | string) {
if (typeof input === "boolean") { ... }
if (typeof input === "number") { ... }
if (typeof input === "string") { ... }
}
Arrays
function func(input: string | string[]) {
if (Array.isArray(input)) {
console.log("input is a string[]");
}
}
Classes (instanceof)
function func(input: Date | string) {
if (input instanceof Date) {
input.getMonth();
}
}
By property (in operator)
type City = { zipCode: string };
type Country = { isoCode: string };
function func(input: City | Country) {
if ("zipCode" in input) {
return input.zipCode;
}
return input.isoCode;
}
Discriminated uniton (tagged union)
interface PushEvent {
type: "Push";
repository: string;
}
interface LoginEvent {
type: "Login";
email: string;
timestamp: string;
}
type Event = PushEvent | LoginEvent;
function processEvent(event: Event) {
switch (event.type) {
case "Login":
// event narrowed to: LoginEvent based on type="Login"
return event.email;
case "Push":
// event narrowed to: PushEvent based on type="Push"
return event.repository;
}
}
Custom type predicates (is)
TODO: