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
typeof
operator can be used to check primitive types likestring
,number
,boolean
, etc.
- The
-
Using
instanceof
:- The
instanceof
operator 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: 42
Explanation:
- The
typeof
operator is used to check ifvalue
is astring
ornumber
. - 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
instanceof
operator checks ifanimal
is an instance ofDog
orCat
. - 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
isFish
function is a user-defined type guard that checks ifpet
is aFish
. - The return type
pet is Fish
is a type predicate that tells TypeScript the type ofpet
within theif
block.
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: true
Exercise 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: 5
Exercise 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: 10
Common 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
typeof
for non-primitive types.- Tip: Use
typeof
for primitive types andinstanceof
for 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