Sling Academy
Home/TypeScript/TypeScript Generics and Intersection Types: A Complete Guide

TypeScript Generics and Intersection Types: A Complete Guide

Last updated: January 08, 2024

Introduction

TypeScript elevates JavaScript to new heights, offering static typing and powerful abstractions. Among its features, Generics and Intersection Types are the linchpins for creating robust and reusable code. Let’s dive into the mechanics and art of utilizing these compelling TypeScript features.

Understanding Generics

Generics provide a way to create reusable components by abstracting types. Think of them as the variables of types – placeholders that allow you to write functions, interfaces, and classes that work with any data type without losing type information.

Example: Here’s a simple generic function that returns whatever is passed to it:

function identity<T>(arg: T): T {
  return arg;
}

The <T> syntax declares a generic that the function can use. This makes the function flexible, yet type-safe.

More Complex Generics

Advanced Generic Function: Let’s build a function that takes an array and a function, and applies the function to each element of the array.

function applyToEach<T, U>(arr: T[], func: (arg: T) => U): U[] {
  return arr.map(func);
}

This utilizes two generics, T and U, representing the types of the array’s elements and the return type of the function, respectively.

Constraints on Generics

Sometimes, you want your generics to follow certain rules or have properties you can access. Constraints let you specify the requirements a generic must meet.

Constrained Generic Function:

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

Here, K is constrained to be a key of T, providing access to property names.

Intersection Types

Intersection Types allow you to combine multiple types into one. They’re great for mixing compatible types or adding additional functionality to existing types.

Simple Intersection: Combining two object types.

type FirstType = {
  id: number;
}

type SecondType = {
  name: string;
}

type CombinedType = FirstType & SecondType;

A CombinedType instance will have both id and name properties.

Advanced Intersection Types

Intersections aren’t limited to objects. You can intersect functions, arrays, and even other generics.

Function Intersection: Creating a callable type with additional properties.

type Loggable<T> = T & { log: (msg: string) => void };
function createLoggable<T extends Function>(func: T): Loggable<T> {
  return Object.assign(func, {
      log: (msg: string) => { console.log(msg); }
  });
}

Now, your functions can have a log method.

Generics with Intersection Types

Bring the power of Generics and Intersection Types together for complex, composable types.

Example: An intersection with a generic constraint.

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

Use Cases and Patterns

Generics and Intersection Types shine in high-reuse scenarios, such as utility functions, data handling, and framework development. For instance, a generic data fetcher with type-specific parsers, or component prop types in a React application.

Limitations and Gotchas

No feature is without its caveats. Beware of overly complex types that can become hard to read or maintain. Moreover, not all types combine neatly into intersections; understanding the nuances of your type structures is paramount.

Conclusion

This guide scratched the surface of TypeScript’s Generics and Intersection Types, showcasing their adaptability and strength. Mastering these constructs equips you with the tools to craft code that’s both scalable and maintainable, all while preserving the intricacies of your data shapes.

Next Article: Adding Type Constraints in TypeScript Generics

Previous Article: TypeScript Generics and Union Types: A Complete Guide

Series: The First Steps to TypeScript

TypeScript

You May Also Like

  • TypeScript: setInterval() and clearInterval() methods (3 examples)
  • TypeScript sessionStorage: CRUD example
  • Using setTimeout() method with TypeScript (practical examples)
  • Working with window.navigator object in TypeScript
  • TypeScript: Scrolling to a specific location
  • How to resize the current window in TypeScript
  • TypeScript: Checking if an element is a descendant of another element
  • TypeScript: Get the first/last child node of an element
  • TypeScript window.getComputerStyle() method (with examples)
  • Using element.classList.toggle() method in TypeScript (with examples)
  • TypeScript element.classList.remove() method (with examples)
  • TypeScript: Adding Multiple Classes to An Element
  • element.insertAdjacentHTML() method in TypeScript
  • TypeScript – element.innerHTML and element.textContent
  • Using element.removeAttribute() method in TypeScript
  • Working with Document.createElement() in TypeScript
  • Using getElementById() method in TypeScript
  • Using Window prompt() method with TypeScript
  • TypeScript – window.performance.measure() method (with examples)