DEV Community

Dharan Ganesan
Dharan Ganesan

Posted on

Day 49: Conditional Types

What are Conditional Types?

Conditional types are a TypeScript feature introduced in version 2.8 that enable you to define types based on conditions. They allow you to express complex type relationships and create reusable type logic. Conditional types are defined using the extends keyword and the ? operator, making them highly versatile.

Basic Syntax

Let's start with the basic syntax of a conditional type:

type MyConditionalType<T> = T extends U ? X : Y;
Enter fullscreen mode Exit fullscreen mode
  • T is the type parameter we want to test.
  • U is the type we're checking T against.
  • X is the type that MyConditionalType evaluates to if the condition is true.
  • Y is the type that MyConditionalType evaluates to if the condition is false.

Understanding the Condition

The condition in a conditional type can be any valid type operation or expression. Commonly used operations include keyof, extends, and infer. Here's a breakdown of some common condition types:

  • keyof: Checks if a type has a specific key.
  • extends: Tests if a type extends another type.
  • infer: Extracts a type from another type.

Basic Examples

Conditional Type with extends

type IsString<T> = T extends string ? true : false;

const a: IsString<"Hello"> = true; // true
const b: IsString<42> = false;     // false
Enter fullscreen mode Exit fullscreen mode

In this example, IsString<T> checks if T extends (is assignable to) the string type. If so, it returns true, otherwise false.

keyof and Index Signatures

type KeysOfType<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
};

interface Person {
  name: string;
  age: number;
  email: string;
}

type StringKeys = KeysOfType<Person, string>; // "name" | "email"
Enter fullscreen mode Exit fullscreen mode

Here, KeysOfType<T, U> generates a new type that contains keys from T whose corresponding values extend U. In our Person interface, it extracts keys with values of type string.

Advanced Examples

Mapping Union Types

type FilterByType<T, U> = T extends U ? T : never;

type OnlyStrings = FilterByType<string | number | boolean, string>; // "string"
Enter fullscreen mode Exit fullscreen mode

This example creates a type that filters out all non-U types from a union type T.

Inferring Function Parameters

type FirstParam<T> = T extends (param1: infer P, ...args: any[]) => any ? P : never;

function greet(name: string, age: number) {
  return `Hello, ${name}! You are ${age} years old.`;
}

type NameParam = FirstParam<typeof greet>; // string
Enter fullscreen mode Exit fullscreen mode

Here, FirstParam<T> extracts the type of the first parameter of a function. In this case, it infers string from the greet function.

Built-in Conditional Types

TypeScript provides several built-in conditional types that simplify common type operations.

Exclude

Exclude<T, U> removes all types from T that are assignable to U.

type Excluded = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
Enter fullscreen mode Exit fullscreen mode

Extract

Extract<T, U> extracts all types from T that are assignable to U.

type Extracted = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
Enter fullscreen mode Exit fullscreen mode

NonNullable

NonNullable<T> removes null and undefined from T.

type NotNullable = NonNullable<string | null | undefined>; // string
Enter fullscreen mode Exit fullscreen mode

Top comments (0)