Conditional Types in Typescript

TypeScript conditional types are a feature that allows developers to create types based on conditional checks, enabling the definition of different types depending on specified conditions. Utilizing the extends keyword, conditional types are particularly useful for scenarios where the type structure needs to dynamically adapt.

The use of the infer keyword facilitates the extraction of type information within the conditional expression, providing a powerful mechanism for creating generic and reusable type definitions. Whether used for checking type relationships, extracting property names based on specific criteria, or handling function overloads, conditional types enhance the expressiveness and flexibility of TypeScript, enabling the creation of more precise and adaptable type definitions in a variety of scenarios.

Basic Conditional Type

type IsString<T> = T extends string ? true : false; const isString: IsString<number> = false; // 'false' because 'number' does not extend 'string' const isString2: IsString<string> = true; // 'true' because 'string' extends 'string'

Conditional Type with infer

type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never; function exampleFunction(): number { return 42; } type ReturnTypeOfExample = ExtractReturnType<typeof exampleFunction>; // ReturnTypeOfExample is number

Distributive Conditional Types

type BoxedValue<T> = { value: T }; type BoxedArray<T> = T extends any[] ? BoxedValue<T[number]>[] : BoxedValue<T>; const boxedString: BoxedArray<string> = { value: "Hello" }; const boxedNumberArray: BoxedArray<number[]> = [{ value: 42 }];

Mapped Types with Conditionals

type StringOrNumberProps<T> = { [K in keyof T]: T[K] extends string number ? K : never; }; interface Example { name: string; age: number; status: boolean; } type AllowedProps = StringOrNumberProps<Example>; // AllowedProps is { name: "name" "age", age: "name" "age" }

Conditional Type for Function Overloading

type OverloadedReturnType<T> = T extends { (arg1: any): infer R1; (arg1: any, arg2: any): infer R2; } ? R1 R2 : never; function overloadedFunction(a: string): number; function overloadedFunction(a: string, b: boolean): string; function overloadedFunction(a: any, b?: any): any { return typeof b === "boolean" ? a.toString() : a.length; } type ReturnTypeOfOverloaded = OverloadedReturnType<typeof overloadedFunction>; // ReturnTypeOfOverloaded is number string

Conditional Types with keyof

type FilterProperties<T, U> = { [K in keyof T]: T[K] extends U ? K : never; }; interface Example { name: string; age: number; status: boolean; } type StringProps = FilterProperties<Example, string>; // StringProps is { name: "name" }

Advanced Use Cases

Conditional types can be used in various scenarios:

  1. Creating type filters for specific property subsets.
  2. Extracting specific types from unions based on conditions.
  3. Defining utility types like Exclude<T, U> which removes a type U from another type T.

Conclusion

Conditional types provide a flexible and expressive way to define types based on conditions, making them a powerful tool for creating generic and reusable type definitions. They are commonly employed in scenarios where the structure of types needs to adapt dynamically, offering increased type safety and code clarity in TypeScript projects.