What are Mapped Types in TypeScript?

What are Mapped Types in TypeScript?

TypeScript has rapidly grown in popularity, transforming the way developers interact with JavaScript by introducing a robust type system. Among the plethora of features TypeScript offers, mapped types are a standout, giving developers enhanced control over object structures. Let’s delve into the world of mapped types and discover their capabilities.

Introduction

TypeScript’s type system is the linchpin of its success. This system adds a layer of safety, ensuring we interact with our data in the way we intended. Mapped types, a gem in TypeScript’s feature-set, allow developers to transform existing types in various ways. This dynamism reduces redundancy and ensures our type definitions are DRY (Don’t Repeat Yourself) and expressive.

Basics of Mapped Types

Mapped types in TypeScript allow you to create new types derived from existing ones by transforming properties. This is done using a syntax that “maps” over the properties of an existing type.

Here’s a basic example:

type MyMappedType = {
[K in keyof ExistingType]: NewType;
};

This construct will iterate over each property (keyof ExistingType) and transform it into the specified NewType.

Common Use Cases

Mapped types are invaluable in a variety of scenarios. Some of the most common include:

  1. Making properties optional or required: This is especially handy when working with interfaces where you want a flexible structure, yet retain the essence of the original interface.
  2. Transforming property values: For example, converting all properties of an object to be of type string regardless of their original type.
  3. Renaming properties: While not a direct feature of mapped types, with a combination of other utilities, you can achieve this behavior.

Built-In Mapped Types

To avoid frequent patterns of type manipulation, TypeScript includes several built-in mapped types:

Partial

Using Partial<T>, all properties of type T become optional. This is useful when you want to accept objects that may not have all the properties of a type.

type Person = {
name: string;
age: number;
};

type PartialPerson = Partial<Person>; // { name?: string; age?: number; }

Required

The opposite of Partial, the Required<T> type ensures all properties of T are mandatory.

type PartialPerson = {
name?: string;
age?: number;
};

type FullPerson = Required<PartialPerson>; // { name: string; age: number; }

Readonly

Ensures that all properties of T are read-only.

type MutablePerson = {
name: string;
age: number;
};

type ImmutablePerson = Readonly<MutablePerson>;

Pick

Allows you to pick specific properties from a type. K is a union of keys from T.

type Person = {
name: string;
age: number;
address: string;
};

type NameAndAge = Pick<Person, 'name' | 'age'>; // { name: string; age: number; }

Record

Creates an object type with keys K and values of type T.

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>;

Creating Custom Mapped Types

To harness the full power of mapped types, sometimes you’ll need to create your own. Here’s the general syntax:

type MyMappedType<T> = {
[P in keyof T]: NewType;
};

For instance, a mapped type converting all properties of an object to boolean can be:

type AllBooleans<T> = {
[P in keyof T]: boolean;
};

Keyof and Index Types

The keyof type operator is pivotal in mapped types. It obtains the keys (property names) of a type as a string union. For instance, keyof Person (where Person has properties name and age) would yield the type 'name' | 'age'.

Index types allow us to model property access dynamically. For instance:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

In the function above, keyof T ensures that the key you want to access exists on the object, and T[K] specifies the type of the property.

To wrap up, mapped types, along with utilities like keyof, empower developers to craft efficient, reusable, and expressive type definitions. As TypeScript evolves, expect more powerful utilities to emerge, further enhancing our type systems. Dive deeper into TypeScript’s official documentation to unlock more intricate use cases and concepts.

Conditional Types and Mapped Types

In TypeScript, conditional types offer a way to define types based on conditions, giving developers a dynamic way to create more flexible type definitions. They usually look something like T extends U ? X : Y, which can be read as: “If type T extends type U, then use type X; otherwise, use type Y.”

On the other hand, mapped types allow us to create new types based on old ones by iterating over property keys of a type. This is akin to mapping over an array, but for object properties. Combining these with conditional types, developers can design types that can adapt based on the given input, making TypeScript even more versatile.

Practical Examples

Consider a scenario where we want to make all properties of a type optional, but only if they are strings:

type ConditionalOptional<T> = {
[K in keyof T]: T[K] extends string ? T[K]? : T[K];
};
type Example = {
name: string;
age: number;
};
type Result = ConditionalOptional<Example>; // { name?: string, age: number }

Modifiers in Mapped Types

With the evolution of TypeScript, the + and - modifiers have been introduced in mapped types. They allow for a more explicit and fine-grained control over the optional and readonly properties in mapped types.

Using Modifiers

The + and - modifiers specifically deal with optional and readonly flags of properties:

  • Using + makes the property optional or readonly.
  • Using - removes the optional or readonly characteristic.

Here’s an example to make it clearer:

1type Original = {
2 +name: string;
3 age?: number;
4};
5
6type Modified = {
7 -name: string;
8 +age: number;
9};
10
11// Results in:
12// {
13// name: string;
14// age: number;
15// }

Best Practices

  1. Ensure Type Safety: Always strive for strong type safety. It ensures that you’re leveraging TypeScript to its fullest, preventing potential runtime errors.
  2. Avoid Over-complication: While mapped types offer a lot of flexibility, avoid creating types that are excessively intricate, as they can become hard to understand and maintain.
  3. Use with Generics: Mapped types shine even brighter when combined with generics. This allows for reusable type transformations that can work across multiple types.

Limitations and Caveats

While mapped types empower TypeScript developers, it’s essential to understand their limitations:

  1. Performance Considerations: Complex mapped types can sometimes lead to increased compilation times. Always measure and be cautious when introducing intricate type transformations in large projects.
  2. Depth Limitations: TypeScript has a certain limit to how deeply it will evaluate nested types. If your mapped types are nested deeply, you might run into issues.

Known Issues (as of 2023)

  • Infer Issues: There are cases where using infer within conditional types inside mapped types can lead to unexpected behaviors.
  • Excess Property Checks: TypeScript sometimes doesn’t perform excess property checks on intersections with mapped types, leading to potential false negatives.

Real-world Examples

Example 1

Imagine a REST API client where we want to make sure that all endpoints return data wrapped in a standard response format:

1type ApiResponse<T> = {
2 data: T;
3 timestamp: Date;
4};
5
6function fetchUser(): ApiResponse<{ name: string, age: number }> {
7 // Implementation here
8}
9
10function fetchPosts(): ApiResponse<Array<{ title: string, content: string }>> {
11 // Implementation here
12}

Example 2

For an e-commerce platform, converting all prices from numbers to strings, but leaving other properties unchanged:

type ConvertPricesToString<T> = {
[K in keyof T]: T[K] extends number ? string : T[K];
};

type Product = {
title: string;
price: number;
};

type UpdatedProduct = ConvertPricesToString<Product>;

Conclusion

Mapped types, combined with features like conditional types and modifiers, are formidable tools in TypeScript’s arsenal. They provide the flexibility needed for advanced type manipulations, promoting more robust and adaptable codebases.

Further Reading and Resources

Official Documentation

Recommended Articles and Tutorials

Books

  • “Programming TypeScript” by Boris Cherny
  • “Effective TypeScript” by Dan Vanderkam

Happy coding and hope to see more TypeScript enthusiasts on codedamn!

Sharing is caring

Did you like what Rishabh Rao wrote? Thank them for their work by sharing it on social media.

0/10000

No comments so far