Advanced TypeScript Patterns: Conditional Types in Real Projects
Conditional types are one of TypeScript's most powerful features, yet most developers only scratch the surface. Here are battle-tested patterns you can apply in production code today.
Conditional types in TypeScript look intimidating at first glance. The syntax feels alien, the error messages are cryptic, and most tutorials stop at trivial examples. But once you internalize a few core patterns, conditional types become an indispensable tool for building truly type-safe APIs.
The Basics: More Than Just Ternaries
At their core, conditional types follow the pattern T extends U ? X : Y. But the real power comes from combining them with infer, which lets you extract types from within other types:
// Extract the return type of an async function
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string
type AlreadyUnwrapped = UnwrapPromise<number>; // number
// Extract props from a React component
type ComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
// Real usage: get props from any component
type ButtonProps = ComponentProps<typeof Button>; // { variant: "primary" | "ghost"; ... }
The infer keyword acts like a pattern-matching variable. TypeScript tries to match the structure, and if it succeeds, binds the inner type to your variable.
Pattern 1: Discriminated API Responses
In a Next.js app, you often have API endpoints that return different shapes based on a status field. Conditional types let you model this precisely:
type ApiResponse<T extends "success" | "error"> = T extends "success"
? { status: "success"; data: unknown; timestamp: number }
: { status: "error"; message: string; code: number };
// The function signature guarantees the return shape matches the status
async function handleRequest<S extends "success" | "error">(
status: S
): Promise<ApiResponse<S>> {
if (status === "success") {
return { status: "success", data: {}, timestamp: Date.now() } as ApiResponse<S>;
}
return { status: "error", message: "Failed", code: 500 } as ApiResponse<S>;
}
Pattern 2: Deep Key Paths
Want to type a function that accesses nested object properties by dot-notation path? Conditional types with template literals make it possible:
type NestedKeyOf<T> = T extends object
? {
[K in keyof T & string]: T[K] extends object
? K | `${K}.${NestedKeyOf<T[K]>}`
: K;
}[keyof T & string]
: never;
interface AppConfig {
db: { host: string; port: number };
cache: { ttl: number; prefix: string };
}
type ConfigPath = NestedKeyOf<AppConfig>;
// "db" | "cache" | "db.host" | "db.port" | "cache.ttl" | "cache.prefix"
function getConfig<P extends ConfigPath>(path: P): string {
// Implementation using path.split(".")
return process.env[path] ?? "";
}
Pattern 3: Filtering Union Members
Conditional types distribute over unions automatically. This lets you filter union members elegantly:
type EventMap = {
click: { x: number; y: number };
keydown: { key: string; code: string };
scroll: { top: number; left: number };
resize: { width: number; height: number };
};
// Extract only events that have a specific property
type EventsWithPosition = {
[K in keyof EventMap]: EventMap[K] extends { x: number } | { top: number }
? K
: never;
}[keyof EventMap];
// "click" | "scroll"
Avoiding Common Pitfalls
Conditional types can cause compilation slowdowns when they're deeply recursive. Keep your recursion depth under 5 levels, and use NoInfer<T> (TypeScript 5.4+) when you need to prevent unwanted inference sites. If your conditional type error messages become unreadable, break them into named intermediate types — readability matters more than cleverness.
Takeaways
- Use
inferto extract types from complex structures instead of manually duplicating them - Combine conditional types with template literals for dot-notation path typing
- Leverage distributive behavior to filter and transform union types
- Keep conditional types shallow and named for maintainability
Conditional types are the backbone of TypeScript's most powerful libraries — ORMs, form validators, API clients. Learning them deeply pays dividends across every project you touch.
Admin
Cal.com
Open source scheduling — tự host booking system, thay thế Calendly. Free & privacy-first.
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!