TypeScript - Conditional Types

Hello, future TypeScript wizards! Today, we're going to embark on an exciting journey into the world of Conditional Types. Don't worry if you're new to programming – I'll be your friendly guide, and we'll take this step-by-step. By the end of this lesson, you'll be amazed at how much you've learned!

TypeScript - Conditional Types

Basic Conditional Types

Let's start with the basics. Conditional Types in TypeScript are like making decisions in your code. Imagine you're at an ice cream shop, and you have to decide between chocolate and vanilla based on whether it's a hot day or not. That's essentially what Conditional Types do in TypeScript!

Here's a simple example:

type IceCreamChoice = boolean extends true ? "Chocolate" : "Vanilla";

In this code, we're saying: "If boolean (which represents true or false) can be true, then choose Chocolate; otherwise, choose Vanilla."

But wait, boolean can always be true, right? So, let's make this more practical:

type WeatherChoice<T> = T extends "Hot" ? "Ice Cream" : "Hot Chocolate";

let summerChoice: WeatherChoice<"Hot"> = "Ice Cream";
let winterChoice: WeatherChoice<"Cold"> = "Hot Chocolate";

Here, we've created a type WeatherChoice that takes a type parameter T. If T extends (or matches) "Hot", we choose "Ice Cream"; otherwise, we choose "Hot Chocolate".

Generic Conditional Types

Now, let's level up! Generic Conditional Types allow us to make our types more flexible and reusable. Think of it like creating a super-flexible ice cream machine that can make different flavors based on what you put in.

type IsArray<T> = T extends any[] ? true : false;

type CheckString = IsArray<string>;  // false
type CheckNumberArray = IsArray<number[]>;  // true

In this example, IsArray is a generic type that checks if the input type T is an array. If it is, it returns true; otherwise, it returns false.

Let's try something more complex:

type ElementType<T> = T extends (infer U)[] ? U : never;

type StringArrayElement = ElementType<string[]>;  // string
type NumberArrayElement = ElementType<number[]>;  // number
type NotArrayElement = ElementType<number>;  // never

Here, ElementType extracts the type of elements in an array. If T is an array, it infers the element type U and returns it. If T is not an array, it returns never (which means "this should never happen").

Conditional Type Constraints

Sometimes, we want to put some restrictions on our types. It's like saying, "You can only have ice cream if you've finished your vegetables!" Let's see how this works in TypeScript:

type NumberOperation<T extends number> = T extends 0 ? "Zero" : "Non-zero";

type ZeroCheck = NumberOperation<0>;  // "Zero"
type NonZeroCheck = NumberOperation<5>;  // "Non-zero"
type InvalidCheck = NumberOperation<"5">;  // Error: Type '"5"' does not satisfy the constraint 'number'.

In this example, NumberOperation only accepts types that extend number. It then checks if the number is 0 or not.

Inferring Within Conditional Types

Last but not least, let's talk about inferring within conditional types. This is like having a super-smart ice cream machine that can figure out what flavor you want based on your mood!

type Flatten<T> = T extends Array<infer U> ? U : T;

type FlattenedNumberArray = Flatten<number[]>;  // number
type FlattenedString = Flatten<string>;  // string

Here, Flatten checks if T is an array. If it is, it infers the element type U and returns it. If not, it returns T as is.

Let's try something more advanced:

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

function greet(name: string): string {
    return `Hello, ${name}!`;
}

type GreetReturn = ReturnType<typeof greet>;  // string

In this example, ReturnType infers the return type of a function. It checks if T is a function type, and if so, it infers and returns the return type R.

Conclusion

Congratulations! You've just taken your first steps into the world of Conditional Types in TypeScript. Remember, like learning to ride a bike, it might feel wobbly at first, but with practice, you'll be zooming around in no time!

Here's a quick reference table of the methods we've covered:

Method Description
Basic Conditional Types Make type decisions based on conditions
Generic Conditional Types Create flexible, reusable conditional types
Conditional Type Constraints Put restrictions on input types
Inferring Within Conditional Types Extract and infer types within conditions

Keep practicing, stay curious, and before you know it, you'll be creating amazing things with TypeScript. Happy coding, future TypeScript masters!

Credits: Image by storyset