Type guards are a way to ensure that a variable or parameter is of a certain type within a specific block of code. They help TypeScript understand the type of a variable at a given point in the code, allowing for more precise type checking and reducing the likelihood of runtime errors.
Key Concepts
-
Type Guards Basics:
- Type guards are expressions that perform runtime checks to ensure a variable is of a specific type.
- They help TypeScript narrow down the type of a variable within a conditional block.
-
Using
typeof:- The
typeofoperator can be used to check primitive types likestring,number,boolean, etc.
- The
-
Using
instanceof:- The
instanceofoperator checks if an object is an instance of a specific class.
- The
-
User-Defined Type Guards:
- Custom functions that return a type predicate to perform more complex type checks.
Practical Examples
Using typeof
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(`String value: ${value}`);
} else {
console.log(`Number value: ${value}`);
}
}
printValue("Hello, TypeScript!"); // Output: String value: Hello, TypeScript!
printValue(42); // Output: Number value: 42Explanation:
- The
typeofoperator is used to check ifvalueis astringornumber. - TypeScript narrows down the type within each block, allowing for type-specific operations.
Using instanceof
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();
}
}
const myDog = new Dog();
const myCat = new Cat();
makeSound(myDog); // Output: Woof!
makeSound(myCat); // Output: Meow!Explanation:
- The
instanceofoperator checks ifanimalis an instance ofDogorCat. - TypeScript narrows down the type within each block, allowing for class-specific method calls.
User-Defined Type Guards
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();
}
}
const myFish: Fish = { swim: () => console.log("Swimming...") };
const myBird: Bird = { fly: () => console.log("Flying...") };
move(myFish); // Output: Swimming...
move(myBird); // Output: Flying...Explanation:
- The
isFishfunction is a user-defined type guard that checks ifpetis aFish. - The return type
pet is Fishis a type predicate that tells TypeScript the type ofpetwithin theifblock.
Practical Exercises
Exercise 1: Using typeof
Write a function describeValue that takes a parameter of type string | number | boolean and logs a different message for each type.
function describeValue(value: string | number | boolean) {
// Your code here
}
// Test cases
describeValue("Hello");
describeValue(100);
describeValue(true);Solution:
function describeValue(value: string | number | boolean) {
if (typeof value === "string") {
console.log(`This is a string: ${value}`);
} else if (typeof value === "number") {
console.log(`This is a number: ${value}`);
} else {
console.log(`This is a boolean: ${value}`);
}
}
// Test cases
describeValue("Hello"); // Output: This is a string: Hello
describeValue(100); // Output: This is a number: 100
describeValue(true); // Output: This is a boolean: trueExercise 2: Using instanceof
Create a function identifyShape that takes a parameter of type Circle | Square and logs a different message for each shape.
class Circle {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
}
class Square {
sideLength: number;
constructor(sideLength: number) {
this.sideLength = sideLength;
}
}
function identifyShape(shape: Circle | Square) {
// Your code here
}
// Test cases
const myCircle = new Circle(10);
const mySquare = new Square(5);
identifyShape(myCircle);
identifyShape(mySquare);Solution:
class Circle {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
}
class Square {
sideLength: number;
constructor(sideLength: number) {
this.sideLength = sideLength;
}
}
function identifyShape(shape: Circle | Square) {
if (shape instanceof Circle) {
console.log(`This is a circle with radius: ${shape.radius}`);
} else {
console.log(`This is a square with side length: ${shape.sideLength}`);
}
}
// Test cases
const myCircle = new Circle(10);
const mySquare = new Square(5);
identifyShape(myCircle); // Output: This is a circle with radius: 10
identifyShape(mySquare); // Output: This is a square with side length: 5Exercise 3: User-Defined Type Guards
Create a function isRectangle that checks if a shape is a Rectangle and use it in a function describeShape to log different messages for Rectangle and Triangle.
interface Rectangle {
width: number;
height: number;
}
interface Triangle {
base: number;
height: number;
}
function isRectangle(shape: Rectangle | Triangle): shape is Rectangle {
// Your code here
}
function describeShape(shape: Rectangle | Triangle) {
// Your code here
}
// Test cases
const myRectangle: Rectangle = { width: 10, height: 20 };
const myTriangle: Triangle = { base: 5, height: 10 };
describeShape(myRectangle);
describeShape(myTriangle);Solution:
interface Rectangle {
width: number;
height: number;
}
interface Triangle {
base: number;
height: number;
}
function isRectangle(shape: Rectangle | Triangle): shape is Rectangle {
return (shape as Rectangle).width !== undefined;
}
function describeShape(shape: Rectangle | Triangle) {
if (isRectangle(shape)) {
console.log(`This is a rectangle with width: ${shape.width} and height: ${shape.height}`);
} else {
console.log(`This is a triangle with base: ${shape.base} and height: ${shape.height}`);
}
}
// Test cases
const myRectangle: Rectangle = { width: 10, height: 20 };
const myTriangle: Triangle = { base: 5, height: 10 };
describeShape(myRectangle); // Output: This is a rectangle with width: 10 and height: 20
describeShape(myTriangle); // Output: This is a triangle with base: 5 and height: 10Common Mistakes and Tips
-
Mistake: Forgetting to use type guards within conditional blocks.
- Tip: Always use type guards to narrow down types before performing type-specific operations.
-
Mistake: Using
typeoffor non-primitive types.- Tip: Use
typeoffor primitive types andinstanceoffor class instances.
- Tip: Use
-
Mistake: Not returning a type predicate in user-defined type guards.
- Tip: Ensure your custom type guard functions return a type predicate (
param is Type).
- Tip: Ensure your custom type guard functions return a type predicate (
Conclusion
Type guards are a powerful feature in TypeScript that help ensure type safety and reduce runtime errors. By using built-in operators like typeof and instanceof, as well as creating user-defined type guards, you can write more robust and maintainable code. Practice using type guards in different scenarios to become proficient in leveraging this feature in your TypeScript projects.
TypeScript Course
Module 1: Introduction to TypeScript
- What is TypeScript?
- Setting Up the TypeScript Environment
- Basic Types
- Type Annotations
- Compiling TypeScript
Module 2: Working with Types
Module 3: Advanced Types
Module 4: Functions and Modules
Module 5: Asynchronous Programming
Module 6: Tooling and Best Practices
- Linting and Formatting
- Testing TypeScript Code
- TypeScript with Webpack
- TypeScript with React
- Best Practices
