Conditional Types
Using ternary operators in types (T extends U ? X : Y) and infer keyword.
🧑🏫 Sabse pehle — simple mein samjho#
Conditional Types TS ki aisi superpower hai jahan types ke andar "if-else" statement lagayi jaa sakti hai. Ye JavaScript ke ternary operator (condition ? true : false) jaisa dikhta hai. Isse API/Library authors aise types likh paate hain jo alag alag conditions pe alag results dete hain.
The Formula#
SomeType extends OtherType ? TrueType : FalseType
Here, extends does not denote object-oriented inheritance. Instead, it signifies "is assignable to" (does the item on the Left match the shape/type of the item on the Right?).
// A basic test type
type IsString<T> = T extends string ? true : false;
type A = IsString<"Hello">; // type A evaluates to true
type B = IsString<123>; // type B evaluates to false
Real World Example (Custom Return Types)#
Imagine an API wrapper function. If I pass raw: true in the fetch options, it returns the raw Response object. Otherwise, it directly returns the JSON data. How do we explain this dynamic behavior to TS?
interface ApiResponse { data: any }
interface RawResponse { headers: any, status: number }
type FetchResult<IsRaw> = IsRaw extends true ? RawResponse : ApiResponse;
// Example Usage:
let result1: FetchResult<true>; // Type evaluates to RawResponse
let result2: FetchResult<false>; // Type evaluates to ApiResponse
The infer Keyword (The Sherlock Holmes 🕵️)#
The biggest advantage of conditional types comes from the infer keyword. infer instructs TypeScript: "I don't know this tiny fragment of the type, please deduce it from the context and extract it for me".
For example, I want TS to tell me the specific type of the items contained inside an array.
type GetArrayItemType<T> = T extends Array<infer ItemType> ? ItemType : T;
// How does it work?
// If T is an Array (T extends Array), then deduce the type inside it (infer ItemType) and return it to me (? ItemType).
// If it is not an array, just give me T back normally (: T).
type StringArrayItem = GetArrayItemType<string[]>; // type is string
type NormalString = GetArrayItemType<string>; // Not an array, so type is string
The ReturnType and Parameters Utility Types (which we studied in Part 2) utilize this exact infer keyword internally to deduce function returns and arguments.
// ReturnType in action internally
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// "If T is a function, deduce (infer) its return type 'R', and hand it over to me."
Distributive Conditional Types#
When you provide a Union Type (e.g., A | B) to a conditional type, TS automatically applies the condition to each member independently and then reforms the result back into a Union. This is exactly how Exclude and Extract utility types are built.
type ToArray<T> = T extends any ? T[] : never;
// If I pass number | string:
type StrNumArr = ToArray<number | string>;
// The result is: number[] | string[] (Note: it does NOT become (number | string)[])