In this section, we will delve into advanced techniques for manipulating types in TypeScript. These techniques are essential for creating more flexible and reusable code, especially in large-scale applications. We will cover the following topics:

  1. Mapped Types
  2. Conditional Types
  3. Template Literal Types
  4. Key Remapping in Mapped Types
  5. Recursive Types

  1. Mapped Types

Mapped types allow you to create new types by transforming properties of an existing type. They are particularly useful for creating variations of a type.

Example: Readonly Type

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

interface User {
  id: number;
  name: string;
}

const user: Readonly<User> = {
  id: 1,
  name: "Alice"
};

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

Explanation

  • Readonly<T> is a mapped type that takes a type T and makes all its properties read-only.
  • [P in keyof T] iterates over each property P in the type T.
  • readonly makes each property read-only.

  1. Conditional Types

Conditional types allow you to create types based on a condition. They are written using the extends keyword.

Example: Extracting Types

type ExtractString<T> = T extends string ? T : never;

type A = ExtractString<string>; // string
type B = ExtractString<number>; // never

Explanation

  • ExtractString<T> checks if T extends string.
  • If T is a string, it returns T; otherwise, it returns never.

  1. Template Literal Types

Template literal types allow you to create new string literal types by combining string literals.

Example: Creating URL Types

type Protocol = "http" | "https";
type Domain = "example.com" | "example.org";

type URL = `${Protocol}://${Domain}`;

const url1: URL = "http://example.com"; // Valid
const url2: URL = "ftp://example.com"; // Error: Type '"ftp://example.com"' is not assignable to type 'URL'.

Explanation

  • ${Protocol}://${Domain} creates a new type by combining Protocol and Domain using template literals.

  1. Key Remapping in Mapped Types

Key remapping allows you to transform the keys of a type in a mapped type.

Example: Prefixing Keys

type PrefixKeys<T, Prefix extends string> = {
  [K in keyof T as `${Prefix}${K & string}`]: T[K];
};

interface User {
  id: number;
  name: string;
}

type PrefixedUser = PrefixKeys<User, "user_">;

// Resulting type:
// type PrefixedUser = {
//   user_id: number;
//   user_name: string;
// }

Explanation

  • [K in keyof T as ${Prefix}${K & string}] remaps the keys of T by prefixing them with Prefix.

  1. Recursive Types

Recursive types are types that reference themselves. They are useful for representing nested structures.

Example: JSON Type

type JSONValue = 
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [key: string]: JSONValue };

const json: JSONValue = {
  name: "Alice",
  age: 30,
  isAdmin: true,
  friends: ["Bob", "Charlie"],
  address: {
    city: "Wonderland",
    zip: "12345"
  }
};

Explanation

  • JSONValue is a recursive type that can be a string, number, boolean, null, an array of JSONValue, or an object with JSONValue properties.

Practical Exercise

Exercise: Create a Deep Readonly Type

Create a type DeepReadonly<T> that makes all properties of T and its nested objects read-only.

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

interface User {
  id: number;
  name: string;
  address: {
    city: string;
    zip: string;
  };
}

const user: DeepReadonly<User> = {
  id: 1,
  name: "Alice",
  address: {
    city: "Wonderland",
    zip: "12345"
  }
};

// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.
// user.address.city = "New City"; // Error: Cannot assign to 'city' because it is a read-only property.

Solution Explanation

  • DeepReadonly<T> makes all properties of T read-only.
  • If a property is an object, it recursively applies DeepReadonly to that property.

Conclusion

In this section, we explored advanced type manipulation techniques in TypeScript, including mapped types, conditional types, template literal types, key remapping in mapped types, and recursive types. These techniques are powerful tools for creating flexible and reusable types, especially in complex applications. Understanding and mastering these concepts will significantly enhance your TypeScript skills and enable you to write more robust and maintainable code.

© Copyright 2024. All rights reserved