Writing Safer Code with Type Guards in TypeScript

0

TypeScript is a superset of JavaScript that offers static type checking. This feature enhances both code stability and readability. Among its features, type guards play a vital role in verifying and narrowing the types of variables. In this article, we will take a closer look at TypeScript’s type guards.

What are Type Guards?

Type guards are used to add logic that checks for specific types within the code, allowing the TypeScript compiler to narrow the type of variables. This enables TypeScript to more accurately infer the type of variables within conditions. Using type guards can help reduce runtime errors and improve type safety.

Types of Type Guards

In TypeScript, there are primarily three types of type guards:

  • `typeof` Type Guard
  • `instanceof` Type Guard
  • Custom Type Guard

1. `typeof` Type Guard

The `typeof` operator is used to check primitive types. For instance, it’s useful for determining whether a value is a string or a number.

function isString(value: any): value is string {
  return typeof value === 'string';
}

function example(value: string | number) {
  if (isString(value)) {
    // TypeScript now knows that 'value' is of type 'string'.
    console.log(value.toUpperCase());
  } else {
    // TypeScript now knows that 'value' is of type 'number'.
    console.log(value.toFixed(2));
  }
}

In the example above, the `isString` function checks if `value` is a string, and within the condition, TypeScript recognizes `value` as a `string`.

2. `instanceof` Type Guard

The `instanceof` operator is used to check if an object is an instance of a particular class.

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

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

function example(pet: Dog | Cat) {
  if (pet instanceof Dog) {
    // TypeScript now knows that 'pet' is of type 'Dog'.
    pet.bark();
  } else {
    // TypeScript now knows that 'pet' is of type 'Cat'.
    pet.meow();
  }
}

In this example, the code checks whether `pet` is an instance of the `Dog` class and narrows the type accordingly.

3. Custom Type Guard

A custom type guard uses the `value is Type` syntax in the return type of a function to narrow the type when certain conditions are met. This allows handling complex object structures or generic types safely.

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

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

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

function example(pet: Fish | Bird) {
  if (isFish(pet)) {
    // TypeScript now knows that 'pet' is of type 'Fish'.
    pet.swim();
  } else {
    // TypeScript now knows that 'pet' is of type 'Bird'.
    pet.fly();
  }
}

In this example, the `isFish` function checks if `pet` is of type `Fish` and narrows the type within the condition.

Examples of Using Type Guards

Type guards can be applied in various situations, including handling complex object structures or generic types.

interface Admin {
  role: 'admin';
  privileges: string[];
}

interface User {
  role: 'user';
  email: string;
}

function isAdmin(account: Admin | User): account is Admin {
  return account.role === 'admin';
}

function logAccountInfo(account: Admin | User) {
  if (isAdmin(account)) {
    // TypeScript now knows that 'account' is of type 'Admin'.
    console.log(`Admin with privileges: ${account.privileges}`);
  } else {
    // TypeScript now knows that 'account' is of type 'User'.
    console.log(`User with email: ${account.email}`);
  }
}

In this example, the `isAdmin` function checks if `account` is of type `Admin` and narrows the type within the condition.

Conclusion

Type Guards in TypeScript are crucial for enhancing code type safety and reducing runtime errors. By using `typeof`, `instanceof`, and custom type guards, you can handle types safely in various situations. Strengthen type safety and write more reliable code by utilizing TypeScript.

Leave a Reply