Type predicates are very useful in TypeScript when you want to assert that a specific value belongs to a specific type within a function, and create a hint to TypeScript about that. A type predicate has the form ‘parameterName is Type‘.
Let’s imagine a simple example. Suppose we have these two classes:
class Circle {
constructor(public radius: number) {}
}
class Square {
constructor(public sideLength: number) {}
Now suppose that we want to create a function that takes a shape (either a ‘Circle‘ or a ‘Square‘) and tests if it is a ‘Circle‘. We can use a type predicate for that:
function isCircle(shape: Circle | Square): shape is Circle {
return (shape as Circle).radius !== undefined;
}
In the function above, we checked if the property ‘radius‘ exists in the ‘shape‘ object. If it exists, we know that it’s a ‘Circle‘. The ‘shape is Circle‘ part of the function signature is the type predicate, implying if the function returns ‘true‘, TypeScript will know that the shape is a ‘Circle‘.
Here’s an example of using our ‘isCircle‘ function:
const circle = new Circle(5);
const square = new Square(4);
if (isCircle(circle)) {
console.log("It's a circle with radius", circle.radius); // TypeScript knows circle is a Circle.
} else {
console.log("It's a square with side length", circle.sideLength); // TypeScript knows circle is a Square.
}
if (isCircle(square)) {
console.log("It's a circle with radius", square.radius); // TypeScript knows square is a Circle.
} else {
console.log("It's a square with side length", square.sideLength); // TypeScript knows square is a Square.
}
In the example above, when we use ‘isCircle(circle)‘, TypeScript knows that if ‘isCircle‘ returns ‘true‘, then ‘circle‘ must actually be an instance of ‘Circle‘. Similarly, when ‘isCircle(square)‘ returns ‘false‘, it knows that ‘square‘ is an instance of ‘Square‘.