Type Guards and Type Assertions
35 minType guards are runtime checks that narrow TypeScript's understanding of a type within a specific code block. They enable you to safely work with union types and unknown types by providing TypeScript with information about the actual type at runtime. Type guards are essential for type-safe programming, especially when dealing with values that could be multiple types or values from external sources.
TypeScript provides several built-in type guards: `typeof` for primitive types, `instanceof` for class instances, and `in` for checking property existence. The `typeof` operator works with primitive types like `string`, `number`, `boolean`, `undefined`, `object`, and `function`. The `instanceof` operator checks if an object is an instance of a specific class or constructor function. The `in` operator checks if a property exists in an object.
Custom type guards are functions that return a type predicate—a boolean return type annotated with `value is Type`. These functions perform runtime checks and tell TypeScript that if the function returns `true`, the value is of the specified type. Custom type guards are powerful for complex type narrowing scenarios where built-in guards aren't sufficient.
Type assertions, on the other hand, tell TypeScript to treat a value as a specific type without performing any runtime checks. They use the `as` keyword or angle bracket syntax (`<Type>value`). Type assertions should be used sparingly and only when you're certain about the type, as they bypass TypeScript's type checking. They're useful when working with DOM APIs, third-party libraries, or when you have external knowledge about types.
The difference between type guards and type assertions is crucial: type guards perform runtime checks and are safe, while type assertions are compile-time only and can lead to runtime errors if used incorrectly. Always prefer type guards over type assertions when possible, as they provide both compile-time and runtime safety.
Understanding when and how to use type guards and type assertions is essential for writing robust TypeScript code. They enable you to work safely with dynamic data, external APIs, and complex type relationships while maintaining type safety throughout your application.
Key Concepts
- Type guards perform runtime checks to narrow types.
- Built-in type guards include typeof, instanceof, and in operators.
- Custom type guards use type predicates (value is Type).
- Type assertions (as) tell TypeScript to treat a value as a specific type.
- Type guards are safer than type assertions because they perform runtime checks.
Learning Objectives
Master
- Using typeof, instanceof, and in operators as type guards
- Creating custom type guard functions with type predicates
- Understanding when to use type guards vs type assertions
- Safely narrowing union types and unknown types
Develop
- Type-safe programming with dynamic data
- Understanding runtime vs compile-time type checking
- Designing effective type guard patterns
Tips
- Use typeof for primitive types, instanceof for class instances, and in for property checks.
- Create custom type guards for complex type narrowing scenarios.
- Prefer type guards over type assertions for safety.
- Use type assertions only when you're certain about the type.
Common Pitfalls
- Using type assertions when type guards would be safer.
- Creating type guards that don't actually check the type correctly.
- Forgetting that type assertions don't perform runtime checks.
- Overusing type assertions, defeating TypeScript's purpose.
Summary
- Type guards perform runtime checks to narrow types safely.
- Built-in type guards include typeof, instanceof, and in.
- Custom type guards use type predicates for complex scenarios.
- Type assertions bypass type checking and should be used sparingly.
- Always prefer type guards over type assertions when possible.
Exercise
Create type guards to safely work with different object types.
interface Car {
brand: string;
model: string;
}
interface Bike {
brand: string;
type: string;
}
function isCar(vehicle: Car | Bike): vehicle is Car {
return 'model' in vehicle;
}
function getVehicleInfo(vehicle: Car | Bike): string {
if (isCar(vehicle)) {
return `Car: ${vehicle.brand} ${vehicle.model}`;
} else {
return `Bike: ${vehicle.brand} ${vehicle.type}`;
}
}
Exercise Tips
- Use typeof for primitive types: if (typeof value === 'string') { ... }
- Use instanceof for class instances: if (value instanceof Date) { ... }
- Create custom type predicates: function isString(value: unknown): value is string { return typeof value === 'string'; }
- Combine type guards with assertion functions for better type narrowing.