DEV Community

Cover image for Mastering TypeScript: Looping with Types
Alexander Opalic
Alexander Opalic

Posted on • Edited on • Originally published at alexop.dev

Mastering TypeScript: Looping with Types

Introduction

Loops play a pivotal role in programming, enabling code execution without redundancy. JavaScript developers might be familiar with foreach or do...while loops, but TypeScript offers its own unique looping capabilities at the type level. This blog post delves into three advanced TypeScript looping techniques, demonstrating their importance and utility.

Mapped Types

Mapped Types in TypeScript allow transformation of object properties. Consider an object requiring immutable properties:

type User = { 
  id: string, 
  email: string, 
  age: number 
};
Enter fullscreen mode Exit fullscreen mode

To create an immutable version of this type, we traditionally hardcode it. However, to maintain adaptability with the original type, Mapped Types come into play. They use generics to map each property, offering flexibility to modify property characteristics. For instance:

type ReadonlyUser<T> = {
  readonly [P in keyof T]: T[P];
};
Enter fullscreen mode Exit fullscreen mode

This technique is extensible. For example, adding nullability:

type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};
Enter fullscreen mode Exit fullscreen mode

Or filtering out certain types:

type ExcludeStrings<T> = {
  [P in keyof T as T[P] extends string ? never : P]: T[P];
};
Enter fullscreen mode Exit fullscreen mode

Understanding the core concept of Mapped Types opens doors to creating diverse, reusable types.

Recursion

Recursion is a cornerstone in TypeScript's type-level programming, especially since state mutation is not an option. Consider applying immutability to all nested properties:

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
Enter fullscreen mode Exit fullscreen mode

Here, TypeScript's compiler recursively ensures every property is immutable, demonstrating the language's depth in handling complex types.

Union Types

Union Types represent a set of distinct types, such as:

type Status = 'Failure' | 'Success';
Enter fullscreen mode Exit fullscreen mode

Creating structured types from unions involves looping over each union member. For instance, constructing a type where each status is an object:

type StatusObject = Status extends infer S ? { status: S } : never;
Enter fullscreen mode Exit fullscreen mode

Conclusion

TypeScript's advanced type system transcends static type checking, providing sophisticated tools for type transformation and manipulation. Mapped Types, Recursion, and Union Types are not mere features but powerful instruments that enhance code maintainability, type safety, and expressiveness. These techniques underscore TypeScript's capability to elegantly handle complex programming scenarios, affirming its status as more than a JavaScript superset but a language that enriches our development experience.


Enjoyed this post? Follow me on X for more Vue and TypeScript content:

@AlexanderOpalic

Top comments (8)

Collapse
 
marco_43 profile image
Marco

quick and on the point, no bla bla. Very good, Sir 👌 I like, how it's possible to "play around" with types. Sometimes it feels well boilerplated, but when you onboard a new coworker and he don't mess with the code, you know why it was worth it.

Collapse
 
alexanderop profile image
Alexander Opalic

Thanks! Yeah, I think most of the advanced stuff from TypeScript is needed only for libraries. But it's fun to do fancy stuff with it.

Collapse
 
mudittiwari2000 profile image
Mudit Tiwari

What does "infer" do here? In the last example.

Collapse
 
randreu28 profile image
Rubén Chiquin

It infers whatever type can be extended from the type Status. In this case it is the strings Failure or Success.

Then the never at the end of the ternary is just to tell the typescript compiler that there never will be anything that does not extend Status

Collapse
 
rajaerobinson profile image
Rajae Robinson • Edited

Great post!

Typescript has a Readonly<T> utility type that does what you specified above without the need for manual implementation. Typescript offers a lot of other useful utility types.

Collapse
 
consciousness_dev profile image
Ario Setiawan

It's better if give example code too :D

Collapse
 
shangting profile image
SHANG-TING • Edited

I think DeepReadonly should look like this

type DeepReadonly<T> = T extends never
  ? T
  : {
      readonly [P in keyof T]: DeepReadonly<T[P]>;
    };
Enter fullscreen mode Exit fullscreen mode
Collapse
 
alexanderop profile image
Alexander Opalic

interesting both solutions works why do you like yours more?

Some comments have been hidden by the post's author - find out more