JavaScript > TypeScript > Advanced TypeScript > Type guards
TypeScript Type Guards: Narrowing Types for Safer Code
Explore TypeScript type guards to refine variable types within conditional blocks, ensuring type safety and enabling specific operations based on type checks. This guide offers practical examples and best practices for effective type narrowing.
Understanding Type Guards
Type guards are TypeScript functions that narrow down the type of a variable within a conditional block. They enable the TypeScript compiler to understand more precisely what type a variable holds, leading to more accurate type checking and fewer errors. Without type guards, TypeScript might not allow you to perform certain operations on a variable because it can't be sure of its type. Type guards provide that certainty.
Basic Type Guard Example: 'typeof' Operator
This example uses the typeof
operator as a type guard. Inside the if
block, TypeScript infers that input
is a string, allowing you to access its length
property. In the else
block, TypeScript knows it's a number, so you can use toFixed()
.
function printLength(input: string | number) {
if (typeof input === 'string') {
// Within this block, TypeScript knows 'input' is a string
console.log(input.length);
} else {
// Within this block, TypeScript knows 'input' is a number
console.log(input.toFixed(2));
}
}
Custom Type Guards: Using Type Predicates
Here, we define a custom type guard function isBird
. The return type pet is Bird
is a type predicate. If isBird(pet)
returns true
, TypeScript knows that pet
is of type Bird
within the if
block. If it returns false
, TypeScript knows it is a Fish
.
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function isBird(pet: Bird | Fish): pet is Bird {
return (pet as Bird).fly !== undefined;
}
function careForPet(pet: Bird | Fish) {
if (isBird(pet)) {
pet.fly();
} else {
pet.swim();
}
}
Using the 'in' Operator as a Type Guard
The in
operator checks if a property exists on an object. In this example, if 'radius' in shape
returns true
, TypeScript infers that shape
is a Circle
. Otherwise, it's a Square
.
interface Circle {
radius: number;
}
interface Square {
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if ('radius' in shape) {
return Math.PI * shape.radius * shape.radius; // TypeScript knows 'shape' is a Circle
} else {
return shape.sideLength * shape.sideLength; // TypeScript knows 'shape' is a Square
}
}
Real-Life Use Case: Handling API Responses
Type guards are useful when dealing with API responses that can be either successful or erroneous. Checking the success
property allows you to narrow down the type and handle the response accordingly.
interface SuccessResponse {
success: true;
data: any;
}
interface ErrorResponse {
success: false;
error: string;
}
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
if (response.success) {
console.log('Data:', response.data);
} else {
console.error('Error:', response.error);
}
}
Best Practices
isString
, isNumber
, isUser
).
Interview Tip
When discussing type guards in an interview, be prepared to explain the purpose of type narrowing, how type guards achieve this, and the different techniques for implementing them (typeof
, instanceof
, custom type predicates, and the in
operator). Also, be prepared to discuss the trade-offs of using type guards versus other type-checking techniques.
When to Use Them
Use type guards when you need to perform operations that are specific to certain types within a union. They are particularly useful when dealing with:
Alternatives
Alternatives to type guards include:
Pros
Cons
FAQ
-
What happens if I don't use a type guard when working with a union type?
TypeScript might prevent you from performing certain operations on the variable because it cannot be sure of its type. You might encounter type errors at compile time. -
Can I use type guards with classes?
Yes, you can use theinstanceof
operator as a type guard to check if an object is an instance of a particular class. -
Are type guards a runtime or compile-time feature?
Type guards primarily affect TypeScript's compile-time type checking. While the type guard functions themselves are executed at runtime, their main purpose is to provide type information to the compiler.