In this section, we will delve into some of the more advanced types available in TypeScript. These types allow for more precise type definitions and can help in creating robust and maintainable code. We will cover the following key concepts:

  1. Intersection Types
  2. Union Types
  3. Type Guards
  4. Type Aliases
  5. Mapped Types
  6. Conditional Types

  1. Intersection Types

Intersection types allow you to combine multiple types into one. This is useful when you want to merge properties from different types.

Example:

interface Person {
  name: string;
  age: number;
}

interface Employee {
  employeeId: number;
}

type EmployeePerson = Person & Employee;

const employee: EmployeePerson = {
  name: "John Doe",
  age: 30,
  employeeId: 1234
};

Explanation:

  • The EmployeePerson type combines Person and Employee, requiring all properties from both interfaces.

  1. Union Types

Union types allow a variable to be one of several types. This is useful when a value can be of different types.

Example:

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}

printId(101); // Valid
printId("202"); // Valid

Explanation:

  • The id parameter can be either a number or a string.

  1. Type Guards

Type guards are used to narrow down the type of a variable within a conditional block.

Example:

function isString(value: any): value is string {
  return typeof value === "string";
}

function printValue(value: number | string) {
  if (isString(value)) {
    console.log("String value: " + value.toUpperCase());
  } else {
    console.log("Number value: " + value.toFixed(2));
  }
}

Explanation:

  • The isString function is a type guard that checks if a value is a string.
  • Within the if block, TypeScript knows value is a string.

  1. Type Aliases

Type aliases allow you to create a new name for a type. This can make complex types easier to work with.

Example:

type StringOrNumber = string | number;

function logValue(value: StringOrNumber) {
  console.log("Value: " + value);
}

Explanation:

  • StringOrNumber is a type alias for string | number.

  1. Mapped Types

Mapped types allow you to create new types by transforming properties of an existing type.

Example:

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

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

const user: Readonly<User> = {
  name: "Alice",
  age: 25
};

// user.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.

Explanation:

  • Readonly<T> creates a new type where all properties of T are read-only.

  1. Conditional Types

Conditional types allow you to create types based on a condition.

Example:

type IsString<T> = T extends string ? "Yes" : "No";

type Test1 = IsString<string>; // "Yes"
type Test2 = IsString<number>; // "No"

Explanation:

  • IsString<T> checks if T is a string and returns "Yes" or "No" accordingly.

Practical Exercise

Exercise:

Create a function merge that takes two objects and returns a new object that combines properties from both. Use intersection types to ensure the return type includes all properties from both input objects.

function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

const mergedObj = merge(obj1, obj2);
console.log(mergedObj); // { a: 1, b: 2, c: 3, d: 4 }

Solution Explanation:

  • The merge function uses generics T and U to accept any two objects.
  • The return type T & U ensures the result includes all properties from both obj1 and obj2.

Conclusion

In this section, we explored advanced TypeScript types that enhance the flexibility and robustness of your code. Understanding and utilizing these types can significantly improve your TypeScript programming skills, especially when working with complex data structures. In the next section, we will explore TypeScript decorators, which provide a powerful way to add metadata and modify classes and methods.

© Copyright 2024. All rights reserved