TypeScript Mapped Types

This chapter focuses on TypeScript mapped types, a powerful feature that allows developers to create new types by transforming existing ones. Mapped types are particularly useful for automating repetitive type transformations, ensuring consistency, and improving code maintainability.

Chapter Goal

  • To understand what mapped types are and their purpose in TypeScript.
  • To learn how to create and use mapped types effectively.
  • To explore practical applications of mapped types in real-world scenarios.

Key Characteristics for TypeScript Mapped Types

  • Type Transformation: Enables creation of new types based on existing ones.
  • Flexibility: Applies modifications to all or selected properties of a type.
  • Built-in Utility Types: Leverages predefined utility types like Partial, Readonly, and Pick.
  • Dynamic Keys: Supports key iteration for dynamic type generation.
  • Customizability: Allows developers to define tailored mapped types for specific needs.

Basic Rules for TypeScript Mapped Types

  1. Use mapped types to iterate over keys of an existing type.
  2. Apply transformations using syntax like [Key in keyof T]: Type.
  3. Combine mapped types with conditional types for advanced logic.
  4. Leverage built-in utility types for common transformations.
  5. Avoid overcomplicating mapped types to maintain readability.

Best Practices

  1. Use meaningful names for mapped type definitions.
  2. Combine mapped types with conditional types for advanced scenarios.
  3. Document custom mapped types for better maintainability.
  4. Prefer built-in utility types for standard transformations.
  5. Test mapped types with diverse inputs to ensure correctness.

Syntax Table

Serial No Component Syntax Example Description
1 Basic Mapped Type type Readonly<T> = { [P in keyof T]: T[P] }; Makes all properties of T readonly.
2 Partial Mapped Type type Partial<T> = { [P in keyof T]?: T[P] }; Makes all properties of T optional.
3 Pick Utility Type type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; Selects a subset of properties from T.
4 Record Utility Type type Record<K extends keyof any, T> = { [P in K]: T }; Creates a type with keys of type K and values of type T.

Syntax Explanation

1. Basic Mapped Type

What is a Basic Mapped Type

A method for iterating over each key in an existing type to systematically apply a transformation, such as modifying properties, changing value types, or imposing constraints, to create a new derived type.

Syntax

type Readonly<T> = {

  [P in keyof T]: T[P];

};

Detailed Explanation

  • The keyof T retrieves all keys of the type T.
  • The [P in keyof T] iterates over each key, applying a transformation.
  • This example creates a type with identical properties to T.

Example

interface User {

  id: number;

  name: string;

}

 

type ReadonlyUser = Readonly<User>;

 

const user: ReadonlyUser = { id: 1, name: ‘Alice’ };

// user.id = 2; // Error: Cannot assign to ‘id’ because it is a read-only property.

Output

// Error: Cannot assign to ‘id’

Notes

  • Use mapped types for consistent transformations across properties.

Warnings

  • Avoid over-transforming types, which can lead to complex and unreadable code.

2. Partial Mapped Type

What is a Partial Mapped Type

A special mapped type in TypeScript that modifies all properties of a given type to make them optional, allowing flexibility when working with partially complete objects.

Syntax

type Partial<T> = {

  [P in keyof T]?: T[P];

};

Detailed Explanation

  • The ? modifier makes each property optional.
  • Useful for scenarios like initializing objects incrementally.

Example

interface User {

  id: number;

  name: string;

}

 

type PartialUser = Partial<User>;

 

const user: PartialUser = { id: 1 };

console.log(user); // Output: { id: 1 }

Output

{ id: 1 }

Notes

  • Use Partial when only some properties of a type need to be defined.

Warnings

  • Ensure optional properties are handled correctly in the consuming code.

3. Pick Utility Type

What is the Pick Utility Type

A utility type in TypeScript designed to create a new type by extracting a subset of properties from an existing type, offering flexibility and precision in type definitions for specific use cases.

Syntax

type Pick<T, K extends keyof T> = {

  [P in K]: T[P];

};

Detailed Explanation

  • The K extends keyof T ensures K is a subset of keys from T.
  • The [P in K] selects only the specified keys.

Example

interface User {

  id: number;

  name: string;

  email: string;

}

 

type UserPreview = Pick<User, ‘id’ | ‘name’>;

 

const user: UserPreview = { id: 1, name: ‘Alice’ };

console.log(user); // Output: { id: 1, name: ‘Alice’ }

Output

{ id: 1, name: ‘Alice’ }

Notes

  • Use Pick to create focused types for specific use cases.

Warnings

  • Avoid overusing Pick, as it may lead to fragmented type definitions.

4. Record Utility Type

What is the Record Utility Type

A utility type in TypeScript used to define a new type where the keys are specified and the values share a common type. This is particularly useful for scenarios requiring consistent mappings, such as permissions, configurations, or dynamic objects.

Syntax

type Record<K extends keyof any, T> = {

  [P in K]: T;

};

Detailed Explanation

  • The K represents the set of keys, and T represents the value type.
  • Creates a consistent structure where all keys share the same value type.

Example

type Permissions = ‘read’ | ‘write’ | ‘delete’;

 

type RolePermissions = Record<Permissions, boolean>;

 

const admin: RolePermissions = {

  read: true,

  write: true,

  delete: true,

};

 

console.log(admin); // Output: { read: true, write: true, delete: true }

Output

{ read: true, write: true, delete: true }

Notes

  • Use Record to define consistent mappings between keys and values.

Warnings

  • Ensure keys and values are correctly aligned to avoid runtime errors.

Real-Life Project

Project Name

Dynamic Form Builder

Project Goal

Demonstrates how to use mapped types to create dynamic form configurations with optional and nullable fields.

Code for This Project

interface FormFields {

  username: string;

  password: string;

  rememberMe: boolean;

}




type FormConfig<T> = {

  [P in keyof T]: {

    label: string;

    value: T[P];

    optional?: boolean;

  };

};




const formConfig: FormConfig<FormFields> = {

  username: { label: 'Username', value: 'JohnDoe' },

  password: { label: 'Password', value: '********' },

  rememberMe: { label: 'Remember Me', value: true, optional: true },

};




console.log(formConfig);

Save and Run

  1. Save the code into files with appropriate names, such as formConfig.ts for the example above.
  2. Open your preferred IDE, such as Visual Studio Code, and ensure that TypeScript is properly configured.
  3. Compile the TypeScript file into JavaScript using the command tsc formConfig.ts in your terminal.
  4. Locate the output JavaScript file in the same directory, typically named formConfig.js.
  5. Run the JavaScript file using Node.js with the command node formConfig.js.
  6. Verify the output in the terminal to ensure it matches the expected result.
  7. Save the code in your development environment.
  8. Compile the TypeScript code into JavaScript using tsc.
  9. Run the resulting JavaScript file using node.

Expected Output

{

  username: { label: ‘Username’, value: ‘JohnDoe’ },

  password: { label: ‘Password’, value: ‘********’ },

  rememberMe: { label: ‘Remember Me’, value: true, optional: true }

}

Insights

  • Mapped types simplify repetitive type transformations, improving maintainability.
  • Built-in utility types like Partial and Readonly provide robust solutions for common scenarios.
  • Real-world examples like dynamic form builders demonstrate their practical utility.

Key Takeaways

  • Mapped types enable efficient type transformations in TypeScript.
  • Combine mapped types with utility types and conditional types for advanced use cases.
  • Test mapped types in diverse scenarios to ensure reliability and correctness.