TypeScript Conditional Types
Last Updated :
24 Jan, 2025
Improve
In TypeScript, conditional types enable developers to create types that depend on a condition, allowing for more dynamic and flexible type definitions.
- They follow the syntax T extends U ? X : Y, meaning if type T is assignable to type U, the type resolves to X; otherwise, it resolves to Y.
- Conditional types are particularly useful for creating utility types and for advanced type manipulations, enhancing code reusability and type safety.
type IsString<T> = T extends string ? 'Yes' : 'No';
type Test1 = IsString<string>;
type Test2 = IsString<number>;
console.log('Test1:', 'Yes');
console.log('Test2:', 'No');
- The IsString type alias uses a conditional type to check if a type T extends the string.
- If T is assignable to string, it resolves to 'Yes'; otherwise, it resolves to 'No'.
- Test1 is evaluated as 'Yes' because the string extends the string.
- Test2 is evaluated as 'No' because the number does not extend the string.
Output:
Test1: Yes
Test2: No
type Num<T> = T extends number[] ? number
: (T extends string[] ? string : never)
// Return num
const num: Num<number[]> = 4;
// Return invalid
const stringnum: Num<number> = "7";
console.log(num, stringnum);
Conditional Type Constraints
Conditional type constraints allow defining constraints on generic types within conditional types, enabling dynamic and precise type handling.
type CheckNum<T> = T extends number ? T : never;
type NumbersOnly<T extends any[]> = {
[K in keyof T]: CheckNum<T[K]>;
};
const num: NumbersOnly<[4, 5, 6, 8]> = [4, 5, 6, 8];
const invalid: NumbersOnly<[4, 6, "7"]> = [4, 6, "7"];
- CheckNum<T> ensures only numbers are retained; other types resolve to never.
- NumbersOnly<T> applies CheckNum to each array element, filtering non-numbers.
Output:
Type '"7"' is not assignable to type 'never'.
Inferring Within Conditional Types
This feature extracts and utilizes types within a conditional type definition, enabling precise transformations.
type ElementType<T> = T extends (infer U)[] ? U : never;
const numbers: number[] = [1, 2, 3];
const element: ElementType<typeof numbers> = numbers[0];
const invalidElement: ElementType<typeof numbers> = "string";
- ElementType<T> extracts element types from arrays using the infer keyword.
- element correctly resolves to number; assigning a string is invalid.
Output:
Type 'string' is not assignable to type 'number'.
Distributive Conditional Types
These types distribute over unions, applying conditional logic to each union member individually.
type Colors = 'red' | 'blue' | 'green';
type ColorClassMap = {
red: 'danger';
blue: 'primary';
green: 'success';
};
type MapColorsToClasses<T extends string> = T extends keyof ColorClassMap
? { [K in T]: ColorClassMap[T] }
: never;
const redClass: MapColorsToClasses<Colors> = { red: 'danger' };
const invalidClass: MapColorsToClasses<Colors> = { yellow: 'warning' };
- MapColorsToClasses<T> checks if T matches a key in ColorClassMap, mapping it accordingly.
- Invalid colors like 'yellow' are rejected because they do not exist in ColorClassMap.
Output:
Type '{ yellow: "warning"; }' is not assignable to type 'never'.
Best Practices of Using TypeScript Conditional Types
- Use conditional types to create flexible, reusable type definitions.
- Combine conditional types with generics for enhanced adaptability.
- Utilize the infer keyword for type inference in complex scenarios.