TypeScript Conditional Types

Conditional types in TypeScript provide a way to create types that depend on a condition. They allow for greater flexibility and expressiveness in type definitions, making it possible to model complex type relationships in a clear and concise manner. This article explores how conditional types work in TypeScript and provides examples to illustrate their use.

What Are Conditional Types?

Conditional types enable the creation of types that are selected based on a condition. They are similar to conditional statements in programming but operate at the type level. The basic syntax of a conditional type is:

type ConditionalType = T extends U ? X : Y;

In this syntax:

  • T is the type being checked.
  • U is the type to compare against.
  • X is the type returned if T extends U.
  • Y is the type returned if T does not extend U.

Basic Example of Conditional Types

Here is a simple example of a conditional type that returns different types based on whether a given type is a string or not:

type IsString = T extends string ? "String" : "Not a string";

type Result1 = IsString;  // Result1 is "String"
type Result2 = IsString;  // Result2 is "Not a string"

In this example, IsString checks if T extends string. If it does, the result is "String"; otherwise, it is "Not a string".

Using Conditional Types with Generic Types

Conditional types can also be used with generic types to create more flexible and reusable type definitions. For example, a type that extracts the return type of a function:

type ReturnType = T extends (...args: any[]) => infer R ? R : never;

type FunctionType = (x: number) => string;

type Result = ReturnType;  // Result is string

In this example, ReturnType uses the infer keyword to infer the return type R of the function type T. If T is a function type, ReturnType will be the return type; otherwise, it defaults to never.

Conditional Types with Union Types

Conditional types can also work with union types to handle multiple possible types. For example, distinguishing between different union members:

type ExtractString = T extends string ? T : never;

type UnionType = string | number | boolean;

type Result = ExtractString;  // Result is string

In this example, ExtractString extracts string from a union type UnionType, resulting in string.

Conditional Types with Type Mappings

Conditional types can be combined with type mappings to create more complex type transformations. For instance, mapping over an array of types to apply a conditional type:

type MapArray = {
  [K in keyof T]: T[K] extends string ? T[K] : never;
};

type ArrayType = [string, number, boolean];

type MappedArray = MapArray;  // MappedArray is [string, never, never]

In this example, MapArray maps over each element of the array T and applies a conditional type to each element, resulting in an array where only string elements are preserved.

Conclusion

Conditional types in TypeScript are a powerful tool for creating flexible and expressive type definitions. By leveraging conditional types, developers can model complex type relationships, handle various scenarios, and improve type safety in their TypeScript code. Understanding how to use conditional types effectively can significantly enhance the ability to write robust and maintainable TypeScript code.