TypeScript type guards are expressions that perform a runtime check on a value and narrow its type within a conditional block. They are useful when you want to discriminate between different types of values and access their specific properties or methods.
There are different ways to write type guards in TypeScript, depending on the kind of types you want to check.
typeof
operatorThis is a built-in operator that returns the type of a value as a string. You can use it to check for primitive types such as number, string, boolean, etc. For example:
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
// padding is narrowed to number
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
// padding is narrowed to string
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
In this example, the typeof
operator acts as a type guard for the padding parameter, which can be either a string or a number. Within each if block, TypeScript knows that padding has a specific type and allows you to use its methods.
instanceof
operatorThis is another built-in operator that checks if a value is an instance of a class or constructor function. You can use it to check for object types that have a prototype chain. For example:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
class Cat extends Animal {
meow() {
console.log("Meow! Meow!");
}
}
function greet(animal: Animal) {
console.log(`Hello, ${animal.name}!`);
if (animal instanceof Dog) {
// animal is narrowed to Dog
animal.bark();
}
if (animal instanceof Cat) {
// animal is narrowed to Cat
animal.meow();
}
}
In this example, the instanceof
operator acts as a type guard for the animal parameter, which can be an instance of Animal or any of its subclasses. Within each if block, TypeScript knows that animal has a specific type and allows you to use its methods.
in
operatorThis is another built-in operator that checks if a property exists on an object or its prototype chain. You can use it to check for object types that have different sets of properties. For example:
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function move(animal: Fish | Bird) {
if ("swim" in animal) {
// animal is narrowed to Fish
animal.swim();
} else {
// animal is narrowed to Bird
animal.fly();
}
}
In this example, the in
operator acts as a type guard for the animal parameter, which can be either a Fish or a Bird. Within each branch, TypeScript knows that animal has a specific type and allows you to use its properties.
These are custom functions that return a boolean value based on some condition. You can use them to check for any kind of types that are not easily distinguished by the built-in operators. For example:
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function isCircle(shape: Shape): shape is Circle {
return shape.kind === "circle";
}
function getArea(shape: Shape) {
if (isCircle(shape)) {
// shape is narrowed to Circle
return Math.PI * shape.radius ** 2;
} else {
// shape is narrowed to Square
return shape.sideLength ** 2;
}
}
In this example, the isCircle
function acts as a user-defined type guard for the shape parameter, which can be either a Circle or a Square. The function uses a type predicate shape is Circle
to tell TypeScript that if it returns true, then shape has the Circle type. Within each branch, TypeScript knows that shape has a specific type and allows you to use its properties.