Skip to main content

Using Type Guards

TypeScript introduces a concept called 'Type Guards', a powerful way to narrow down the type of an object within a conditional block. This is particularly useful when dealing with union types where the type could be one of several options.

What is a Type Guard?

A Type Guard is a some form of check that narrows down the type of a variable within a certain scope. This is typically done using some type checking functions or operators.

TypeScript defines a couple of different methods we can use to narrow down types. Let's dive into these methods now.

typeof Type Guards

The typeof type guard is used to discriminate types based on their typeof identifier. This is beneficial when dealing with primitive types like string, number, boolean, object, symbol, and undefined.

function doSomething(x: string | number) {
if (typeof x === 'string') {
// In this block TypeScript knows that `x` must be a string
console.log(x.subtr(1)); // OK
} else {
// In this block TypeScript knows that `x` must be a number
console.log(x.toFixed(2)); // OK
}
}

In the example above, TypeScript is aware of the type of x in each block due to the typeof type guard.

instanceof Type Guards

The instanceof type guard is useful when dealing with class instances. It checks if an object is an instance of a specific class or constructor function.

class Foo {
foo = 0;

common = 'common';
}

class Bar {
bar = 0;

common = 'common';
}

function doSomething(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // OK
console.log(arg.bar); // Error
} else {
console.log(arg.foo); // Error
console.log(arg.bar); // OK
}
}

In this example, TypeScript knows that arg is of type Foo within the first block, and Bar within the second block, thanks to the instanceof type guard.

User-Defined Type Guards

We can also define our own type guards. These are functions which return a boolean, telling us whether an object is of a certain type or not.

interface Bird {
fly(): void;
layEggs(): void;
}

interface Fish {
swim(): void;
layEggs(): void;
}

function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

let pet: Fish | Bird;

// 'pet is Fish' is our type guard
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}

In this example, isFish is a User-Defined Type Guard. It checks whether the pet can swim or not and narrows down the type accordingly.

Conclusion

Type Guards in TypeScript allow us to narrow down types within our code, providing us with more type safety. They can be particularly useful when dealing with union types, but can also be used in a variety of different scenarios.

By combining the power of typeof, instanceof, and user-defined type guards, we can ensure that our code is more robust and less prone to runtime errors.