In this tutorial, we'll explore type guards in TypeScript and learn how to use them effectively in our code.


What are Type Guards?

TypeScript is a statically-typed language, which means that variable types are known at compile-time. However, JavaScript is dynamically-typed, so it's possible for a variable to have a different type at runtime than what was expected at compile-time. This can cause errors in our code if we try to perform operations on a variable that isn't of the expected type.

Type guards provide a way to check the type of a variable at runtime, which allows us to write code that can handle different types of data more safely. There are several ways to implement type guards in TypeScript, which we'll explore in this tutorial.


Using typeof

One of the simplest type guards in TypeScript is the typeof operator. The typeof operator returns the type of a variable as a string, so we can use it to check if a variable is of a specific type. Here's an example:

function printLength(strOrArr: string | string[]) {
  if (typeof strOrArr === 'string') {
    console.log(strOrArr.length);
  } else {
    console.log(strOrArr.length);
  }
}


In this example, the printLength function takes a parameter strOrArr that can be either a string or an array of strings. We use the typeof operator to check if strOrArr is a string, and if so, we print its length. If it's an array, we print the length of the array instead.


Using instanceof

Another way to implement a type guard is to use the instanceof operator. The instanceof operator checks if an object is an instance of a specific class or constructor function. Here's an example:

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

function makeSound(animal: Animal) {
  if (animal instanceof Dog) {
    console.log('Woof!');
  } else if (animal instanceof Cat) {
    console.log('Meow!');
  } else {
    console.log('Unknown animal!');
  }
}

makeSound(new Dog()); // Output: Woof!
makeSound(new Cat()); // Output: Meow!
makeSound(new Animal()); // Output: Unknown animal!


In this example, we define three classes: Animal, Dog, and Cat. We then define a function makeSound that takes an Animal object as a parameter. Inside the function, we use the instanceof operator to check if the animal is a Dog or a Cat, and if so, we print the appropriate sound. If the animal is not a Dog or a Cat, we print "Unknown animal!".


Using custom type guards

We can also create our own custom type guards in TypeScript. A custom type guard is a function that takes a variable as a parameter and returns a boolean value indicating whether the variable is of a specific type. Here's an 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)) {
    return Math.PI * shape.radius *  shape.radius;
} else {
return shape.sideLength ** 2;
}
}

In this example, we define two interfaces, Circle and Square, that represent a circle and a square respectively. We also define a Shape type that can be either a Circle or a Square.

We then define a custom type guard function called isCircle, which takes a Shape object as a parameter and returns a boolean indicating whether the object is a Circle. The function checks the kind property of the Shape object to determine its type.

Finally, we define a function getArea that takes a Shape object as a parameter and returns its area. Inside the function, we use the isCircle type guard to determine whether the Shape object is a Circle or a Square, and we calculate the area accordingly.


Conclusion

TypeScript's type guards provide a powerful way to handle different types of data in a safe and efficient manner. By using type guards, we can write code that is more robust and less prone to errors. We can use the typeof operator, the instanceof operator, or create our own custom type guards to check the type of a variable at runtime. With these tools, we can write TypeScript code that is more reliable and easier to maintain.