Narrowing
TypeScript narrowing is the process of refining the type of a variable within a conditional block. This is useful when working with union types, where a variable can have more than one type.
Narrowing types can occur in different ways, including:
Conditions
By using conditional statements, such as if
or switch
, TypeScript can narrow down the type based on the outcome of the condition. For example:
let x: number | undefined = 10;
if (x !== undefined) {
x += 100; // The type is number, which had been narrowed by the condition
}
Throwing or returning
Throwing an error or returning early from a branch can be used to help TypeScript narrow down a type. For example:
let x: number | undefined = 10;
if (x === undefined) {
throw 'error';
}
x += 100;
Other ways to narrow down types in TypeScript include:
-
instanceof
operator: Used to check if an object is an instance of a specific class. -
in
operator: Used to check if a property exists in an object. -
typeof
operator: Used to check the type of a value at runtime. -
Built-in functions like
Array.isArray()
: Used to check if a value is an array.
TypeScript recognizes several ways to narrow the type:
typeof type guards
The typeof type guard is one specific type guard in TypeScript that checks the type of a variable based on its built-in JavaScript type.
const fn = (x: number | string) => {
if (typeof x === 'number') {
return x + 1; // x is number
}
return -1;
};
Truthiness narrowing
Truthiness narrowing in TypeScript works by checking whether a variable is truthy or falsy to narrow its type accordingly.
const toUpperCase = (name: string | null) => {
if (name) {
return name.toUpperCase();
} else {
return null;
}
};
Equality narrowing
Equality narrowing in TypeScript works by checking whether a variable is equal to a specific value or not, to narrow its type accordingly.
It is used in conjunction with switch
statements and equality operators such as ===
, !==
, ==
, and !=
to narrow down types.
const checkStatus = (status: 'success' | 'error') => {
switch (status) {
case 'success':
return true;
case 'error':
return null;
}
};
In Operator narrowing
The in
Operator narrowing in TypeScript is a way to narrow the type of a variable based on whether a property exists within the variable’s type.
type Dog = {
name: string;
breed: string;
};
type Cat = {
name: string;
likesCream: boolean;
};
const getAnimalType = (pet: Dog | Cat) => {
if ('breed' in pet) {
return 'dog';
} else {
return 'cat';
}
};
instanceof narrowing
The instanceof
operator narrowing in TypeScript is a way to narrow the type of a variable based on its constructor function, by checking if an object is an instance of a certain class or interface.
class Square {
constructor(public width: number) {}
}
class Rectangle {
constructor(
public width: number,
public height: number
) {}
}
function area(shape: Square | Rectangle) {
if (shape instanceof Square) {
return shape.width - shape.width;
} else {
return shape.width - shape.height;
}
}
const square = new Square(5);
const rectangle = new Rectangle(5, 10);
console.log(area(square)); // 25
console.log(area(rectangle)); // 50
Assignments
TypeScript narrowing using assignments is a way to narrow the type of a variable based on the value assigned to it. When a variable is assigned a value, TypeScript infers its type based on the assigned value, and it narrows the type of the variable to match the inferred type.
>tags:
#toFixed [[Math]] #toUpperCase
let value: string | number;
value = 'hello';
if (typeof value === 'string') {
console.log(value.toUpperCase());
}
value = 42;
if (typeof value === 'number') {
console.log(value.toFixed(2));
}
Another form of widening
Ref.to widening to findings const x= 'x'
is a narrow type in spite of let
!