43. Understanding Type Guards

Listen in audio

In the world of TypeScript, understanding type guards is crucial for developers who want to leverage the full power of static typing while still enjoying the flexibility and expressiveness of JavaScript. Type guards are a mechanism that allows you to narrow down the type of a variable within a conditional block, providing more precise type information and enabling more robust and error-free code.

At its core, a type guard is an expression that performs a runtime check to ensure a variable is of a certain type. If the check succeeds, TypeScript can infer the type of the variable within the scope of the guard, allowing developers to safely access properties and methods specific to that type without risking runtime errors.

Type guards can be implemented in various ways, ranging from simple checks using the typeof and instanceof operators to more complex custom type guard functions. Each approach serves different use cases and offers varying levels of type safety and expressiveness.

Using typeof for Primitive Types

The typeof operator is a straightforward way to implement type guards for primitive types such as string, number, boolean, and symbol. By checking the result of typeof, you can narrow down the type of a variable within a conditional block.

function printValue(value: string | number) {
    if (typeof value === 'string') {
        console.log('String value:', value.toUpperCase());
    } else {
        console.log('Number value:', value.toFixed(2));
    }
}

In the example above, the typeof operator is used to determine if value is a string. Within the if block, TypeScript knows that value is a string, allowing us to call toUpperCase() without any type errors. Similarly, in the else block, TypeScript infers that value is a number, enabling the use of toFixed().

Using instanceof for Class Instances

For objects created with classes, the instanceof operator is a powerful tool for type guarding. This operator checks if an object is an instance of a specific class or constructor function, allowing you to narrow down the type of an object based on its prototype chain.

class Dog {
    bark() {
        console.log('Woof!');
    }
}

class Cat {
    meow() {
        console.log('Meow!');
    }
}

function makeSound(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        animal.bark();
    } else {
        animal.meow();
    }
}

In this example, instanceof is used to determine if animal is an instance of the Dog class. Within the if block, TypeScript infers that animal is a Dog, allowing us to call bark(). Conversely, in the else block, TypeScript knows that animal is a Cat, enabling the use of meow().

Creating Custom Type Guard Functions

While typeof and instanceof are useful for many scenarios, custom type guard functions offer greater flexibility and can handle more complex type-checking logic. A custom type guard function is a function that returns a boolean value and uses a is type predicate to inform TypeScript of the type narrowing.

interface Fish {
    swim: () => void;
}

interface Bird {
    fly: () => void;
}

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

function move(pet: Fish | Bird) {
    if (isFish(pet)) {
        pet.swim();
    } else {
        pet.fly();
    }
}

In this example, the isFish function is a custom type guard that checks if a pet object has a swim method. The pet is Fish return type is a type predicate, which tells TypeScript that if isFish returns true, then pet is a Fish. This enables TypeScript to narrow down the type of pet within the if block, allowing us to safely call swim().

Discriminated Unions

Another powerful pattern in TypeScript is the use of discriminated unions, which combine union types with a common property (the discriminant) to enable type narrowing. This pattern is particularly useful when working with complex data structures that can take on multiple shapes.

interface Square {
    kind: 'square';
    size: number;
}

interface Rectangle {
    kind: 'rectangle';
    width: number;
    height: number;
}

interface Circle {
    kind: 'circle';
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(shape: Shape): number {
    switch (shape.kind) {
        case 'square':
            return shape.size * shape.size;
        case 'rectangle':
            return shape.width * shape.height;
        case 'circle':
            return Math.PI * shape.radius * shape.radius;
        default:
            return assertNever(shape);
    }
}

function assertNever(x: never): never {
    throw new Error("Unexpected object: " + x);
}

In this example, the Shape type is a discriminated union with a kind property serving as the discriminant. The area function uses a switch statement to narrow down the type of shape based on its kind. This pattern not only ensures type safety but also provides exhaustive type checking, as TypeScript will issue an error if a new shape type is added without updating the switch statement.

Conclusion

Type guards are an essential feature of TypeScript, providing developers with the tools to write safer and more expressive code. By effectively using typeof, instanceof, custom type guard functions, and discriminated unions, you can take full advantage of TypeScript's type system to create robust applications. Understanding and implementing type guards will not only improve the quality of your code but also enhance your ability to handle complex data structures and logic in a type-safe manner.

As you continue to explore TypeScript, remember that type guards are just one of many powerful features available to you. By mastering type guards, you'll be well-equipped to tackle even the most challenging type-related problems in your JavaScript projects.

Now answer the exercise about the content:

What is the purpose of type guards in TypeScript?

You are right! Congratulations, now go to the next page

You missed! Try again.

Article image Mapped Types in TypeScript

Next page of the Free Ebook:

74Mapped Types in TypeScript

8 minutes

Earn your Certificate for this Course for Free! by downloading the Cursa app and reading the ebook there. Available on Google Play or App Store!

Get it on Google Play Get it on App Store

+ 6.5 million
students

Free and Valid
Certificate with QR Code

48 thousand free
exercises

4.8/5 rating in
app stores

Free courses in
video, audio and text