Type predicates are a powerful feature in TypeScript that allow you to create custom type guards to narrow down types in your code. Here's a simple tutorial on how to use them effectively.
What are Type Predicates?
A type predicate is a special return type for a function that tells TypeScript that the function is checking whether a variable is of a specific type. The syntax is:
function isType(param: SomeType | OtherType): param is SomeType {
// logic to determine if param is SomeType
}
Basic Example
Let's start with a simple example:
// Define two types
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
// Create a type predicate function
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// Use the type predicate to narrow down types
function moveAnimal(animal: Fish | Bird) {
if (isFish(animal)) {
// TypeScript knows animal is Fish here
animal.swim();
} else {
// TypeScript knows animal is Bird here
animal.fly();
}
}
Step-by-Step Guide
- Define Your Types First, define the types you want to distinguish between:
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
- Create a Type Predicate Function Create a function that returns a type predicate:
function isCircle(shape: Shape): shape is Circle {
return shape.kind === "circle";
}
The shape is Circle part is the type predicate. It tells TypeScript that if this function returns true, the parameter is of type Circle.
- Use the Type Predicate for Type Narrowing Now you can use your predicate function to narrow types:
function getArea(shape: Shape) {
if (isCircle(shape)) {
// TypeScript knows shape is Circle here
return Math.PI * shape.radius ** 2;
} else {
// TypeScript knows shape is Square here
return shape.sideLength ** 2;
}
}
Using Type Predicates with Arrays
Type predicates are especially useful when filtering arrays:
Copy Code
const shapes: Shape[] = [
{ kind: "circle", radius: 5 },
{ kind: "square", sideLength: 10 },
{ kind: "circle", radius: 3 }
];
// Filter to get only circles
const circles: Circle[] = shapes.filter(isCircle);
// You can also define inline predicates
const squares: Square[] = shapes.filter((shape): shape is Square => shape.kind === "square");
More Advanced Example: Complex Type Predicates
You can create more complex type predicates that check multiple conditions:
interface User {
name: string;
email?: string;
phone?: string;
}
// Check if a user has complete contact information
function hasContactInfo(user: User): user is User & { email: string; phone: string } {
return typeof user.email === "string" && typeof user.phone === "string";
}
function contactUser(user: User) {
if (hasContactInfo(user)) {
// TypeScript knows user has both email and phone here
sendEmail(user.email);
sendSMS(user.phone);
} else {
// Handle incomplete contact info
}
}
Best Practices
- Keep predicates simple: Focus on checking one specific aspect of a type.
- Use descriptive names: Name your predicate functions clearly (e.g., isCircle, hasContactInfo).
- Avoid side effects: Type predicates should be pure functions without side effects.
- Consider using discriminated unions: They work well with type predicates.
- By using type predicates, you can write more type-safe code that's also more readable and maintainable.
Top comments (0)