This chapter explores TypeScript conditional types, a powerful feature for performing type logic based on conditions. Conditional types enable developers to create dynamic and flexible types that adapt to varying inputs and use cases, providing enhanced type safety and maintainability.
Chapter Goal
- To understand what conditional types are and their purpose in TypeScript.
- To learn how to create and use conditional types effectively.
- To explore practical applications of conditional types in real-world scenarios.
Key Characteristics for TypeScript Conditional Types
- Type Logic: Performs checks and applies different types based on conditions.
- Flexibility: Adapts dynamically to varying inputs.
- Key Syntax: Uses T extends U ? X : Y to define conditions.
- Combination with Utility Types: Works seamlessly with mapped types and utility types.
- Dynamic Decision Making: Creates adaptive and reusable type definitions.
Basic Rules for TypeScript Conditional Types
- Use the extends keyword to define the condition.
- Return one type if the condition is true and another if false.
- Combine conditional types with utility types for advanced transformations.
- Test conditional types with diverse inputs to ensure correctness.
- Avoid overly complex conditional logic for readability.
Best Practices
- Use conditional types to handle varying type scenarios dynamically.
- Combine conditional types with mapped and utility types for enhanced functionality.
- Document complex conditional types for clarity and maintainability.
- Limit nested conditional logic to avoid reducing code readability.
- Test conditional types thoroughly with edge cases to ensure correctness.
Syntax Table
Serial No | Component | Syntax Example | Description | |
1 | Basic Conditional Type | type IsString<T> = T extends string ? true : false; | Checks if T is a string. | |
2 | Nested Conditional Type | type DeepCheck<T> = T extends Array<infer U> ? U extends string ? true : false : never; | Checks nested conditions for arrays. | |
3 | Default Type with Condition | type WithDefault<T> = T extends undefined ? string : T; | Assigns a default type when T is undefined. | |
4 | Combining with Mapped Types | `type Nullable = { [P in keyof T]: T[P] | null };` | Adds nullability to each property of T. |
5 | Infer Keyword Usage | type ElementType<T> = T extends Array<infer U> ? U : never; | Extracts element type from an array. |
Syntax Explanation
1. Basic Conditional Type
What is a Basic Conditional Type
A type construct in TypeScript that dynamically evaluates a condition and determines the output type based on whether the condition evaluates to true or false. This mechanism allows for flexible and adaptive type definitions tailored to specific scenarios.
Syntax
type IsString<T> = T extends string ? true : false;
Detailed Explanation
- The extends keyword checks if T extends string.
- Returns true if T is a string; otherwise, returns false.
Example
type Result1 = IsString<string>; // true
type Result2 = IsString<number>; // false
Output
true
false
Notes
- Use basic conditional types for simple type checks.
Warnings
- Avoid overloading conditional types with unnecessary logic.
2. Nested Conditional Type
What is a Nested Conditional Type
A TypeScript construct that evaluates multiple conditions hierarchically, enabling developers to define types dynamically based on complex, nested criteria. This approach is especially useful for refining types in layered or deeply structured scenarios.
Syntax
type DeepCheck<T> = T extends Array<infer U> ? U extends string ? true : false : never;
Detailed Explanation
- The T extends Array<infer U> checks if T is an array and extracts its element type (U).
- The U extends string checks if the array elements are strings.
Example
type Check1 = DeepCheck<string[]>; // true
type Check2 = DeepCheck<number[]>; // false
Output
true
false
Notes
- Use nested conditional types for complex type hierarchies.
Warnings
- Keep nested logic readable and well-documented.
3. Default Type with Condition
What is a Default Type with Condition
A TypeScript construct that dynamically assigns a fallback type when a specified condition is satisfied, ensuring robust type handling in scenarios where a primary type might be undefined or missing.
Syntax
type WithDefault<T> = T extends undefined ? string : T;
Detailed Explanation
- The T extends undefined checks if T is undefined.
- Returns string if true; otherwise, returns T.
Example
type Result1 = WithDefault<undefined>; // string
type Result2 = WithDefault<number>; // number
Output
string
number
Notes
- Use default types to handle missing or undefined values.
Warnings
- Ensure default types align with the intended use case.
5. Infer Keyword Usage
What is the Infer Keyword
A specialized TypeScript construct that allows extracting and assigning a specific type from a conditional expression, enabling more dynamic and precise type inference in complex scenarios.
Syntax
type ElementType<T> = T extends Array<infer U> ? U : never;
Detailed Explanation
- The T extends Array<infer U> checks if T is an array.
- The infer U extracts the element type of the array.
Example
type Type1 = ElementType<string[]>; // string
type Type2 = ElementType<number[]>; // number
Output
string
number
Notes
- Use infer to extract types dynamically from complex structures.
Warnings
- Ensure extracted types align with the expected structure.
Real-Life Project
Project Name
Dynamic Data Validator
Project Goal
Demonstrates how to use conditional types to validate and process data dynamically based on its type.
Code for This Project
interface Validator<T> {
validate: (input: T) => boolean;
}
type CreateValidator<T> = T extends string
? { type: 'string'; validate: (input: string) => boolean; }
: T extends number
? { type: 'number'; validate: (input: number) => boolean; }
: never;
const stringValidator: CreateValidator<string> = {
type: 'string',
validate: (input) => input.length > 0,
};
const numberValidator: CreateValidator<number> = {
type: 'number',
validate: (input) => input > 0,
};
console.log(stringValidator.validate('Hello')); // Output: true
console.log(numberValidator.validate(-5)); // Output: false
Save and Run
- Save the code in your development environment.
- Compile the TypeScript code using tsc.
- Run the resulting JavaScript file using node.
Expected Output
true
false
Insights
- Conditional types enable dynamic and flexible type logic.
- Combining conditional types with mapped types enhances their versatility.
- Real-world examples like dynamic validators demonstrate their utility.
Key Takeaways
- Conditional types provide powerful tools for dynamic type definitions.
- Combine with utility and mapped types for advanced use cases.
- Test conditional types thoroughly to ensure they handle diverse scenarios.