Mastering TypeScript: Advanced Patterns
TypeScript has gained immense popularity among developers for its powerful type system that helps catch errors early and enhances code quality. While basic types and interfaces are useful, mastering advanced TypeScript patterns can take your code to the next level.
Discriminated Unions
Discriminated unions are a pattern where you use a property to discriminate between different types in a union. This is particularly useful for handling different variants of a data structure.
type Success = {
status: 'success';
data: User[];
};
type Error = {
status: 'error';
error: string;
};
type Response = Success | Error;
function handleResponse(response: Response) {
if (response.status === 'success') {
// TypeScript knows that response is Success
return response.data;
} else {
// TypeScript knows that response is Error
throw new Error(response.error);
}
}
Conditional Types
Conditional types allow you to create types that depend on conditions. They’re similar to ternary operators but for types.
type IsString<T> = T extends string ? true : false;
// Usage
type A = IsString<string>; // true
type B = IsString<number>; // false
Mapped Types
Mapped types allow you to create new types based on existing ones by transforming properties. They’re particularly useful for creating utility types.
type Optional<T> = {
[P in keyof T]?: T[P];
};
// Usage
type User = {
id: number;
name: string;
email: string;
};
type PartialUser = Optional<User>;
// Equivalent to { id?: number; name?: string; email?: string; }
Utility Types
TypeScript provides several built-in utility types that solve common type manipulation needs, such as Partial, Required, Pick, Omit, Exclude, Extract, and many more.
type User = {
id: number;
name: string;
email: string;
password: string;
};
type PublicUser = Omit<User, 'password'>;
// Equivalent to { id: number; name: string; email: string; }
Generic Constraints
Generic constraints allow you to limit the types that can be used with a generic type parameter. This is useful when you need to ensure certain properties exist on the types you’re working with.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Usage
const user = { id: 1, name: 'Brandon Dement' };
const id = getProperty(user, 'id'); // Works
const age = getProperty(user, 'age'); // Error: 'age' is not assignable to parameter of type 'id' | 'name'
Conclusion
Mastering these advanced TypeScript patterns will help you write more robust, maintainable code. They provide the tools to express complex relationships between types and catch errors early, leading to a better development experience and higher-quality software.