TypeScript - Generic Interfaces

Hello there, future coding superstar! Today, we're going to embark on an exciting journey into the world of TypeScript and explore one of its most powerful features: Generic Interfaces. Don't worry if you're new to programming – I'll be your friendly guide, and we'll take this step-by-step. By the end of this lesson, you'll be amazed at how much you've learned!

TypeScript - Generic Interfaces

What are Generic Interfaces?

Before we dive into generic interfaces, let's quickly recap what interfaces are in TypeScript. An interface is like a contract that defines the structure of an object. It tells us what properties and methods an object should have.

Now, imagine if we could make these interfaces more flexible, able to work with different types of data. That's where generic interfaces come in! They allow us to create interfaces that can adapt to various data types, making our code more reusable and versatile.

Basic Generic Interface

Let's start with a simple example:

interface Box<T> {
  contents: T;
}

let numberBox: Box<number> = { contents: 42 };
let stringBox: Box<string> = { contents: "Hello, TypeScript!" };

In this example, Box is a generic interface. The <T> is like a placeholder for a type that we'll specify later. We can use this interface to create boxes that can hold different types of items:

  • numberBox is a Box that holds a number.
  • stringBox is a Box that holds a string.

Isn't that cool? It's like having a magical box that can adapt to hold whatever we put in it!

Multiple Type Parameters

Generic interfaces can have more than one type parameter. Let's look at an example:

interface Pair<T, U> {
  first: T;
  second: U;
}

let pair1: Pair<number, string> = { first: 1, second: "one" };
let pair2: Pair<boolean, Date> = { first: true, second: new Date() };

Here, Pair is a generic interface with two type parameters, T and U. This allows us to create pairs of items where each item can be of a different type. It's like creating a dynamic duo of any two types we want!

Generic Interfaces with Methods

Interfaces can also include methods, and these methods can use the generic types. Let's see an example:

interface Reversible<T> {
  data: T[];
  reverse(): T[];
}

class NumberArray implements Reversible<number> {
  constructor(public data: number[]) {}

  reverse(): number[] {
    return this.data.slice().reverse();
  }
}

let numbers = new NumberArray([1, 2, 3, 4, 5]);
console.log(numbers.reverse()); // Output: [5, 4, 3, 2, 1]

In this example, Reversible is a generic interface that includes a method reverse(). The NumberArray class implements this interface for numbers. The beauty of this approach is that we could easily create similar classes for strings, objects, or any other type!

Generic Interface as a Function Type

Now, let's explore how we can use generic interfaces to describe function types. This is where things get really interesting!

interface Transformer<T, U> {
  (input: T): U;
}

let stringToNumber: Transformer<string, number> = (input) => parseInt(input);

console.log(stringToNumber("42")); // Output: 42

In this example, Transformer is a generic interface that describes a function. It takes an input of type T and returns a value of type U. We then create a function stringToNumber that transforms a string to a number using this interface.

Real-world Example: Data Processor

Let's look at a more complex example that you might encounter in real-world programming:

interface DataProcessor<T, U> {
  processItem(item: T): U;
  processArray(items: T[]): U[];
}

class StringToNumberProcessor implements DataProcessor<string, number> {
  processItem(item: string): number {
    return parseInt(item);
  }

  processArray(items: string[]): number[] {
    return items.map(item => this.processItem(item));
  }
}

let processor = new StringToNumberProcessor();
console.log(processor.processItem("42"));          // Output: 42
console.log(processor.processArray(["1", "2", "3"])); // Output: [1, 2, 3]

In this example, we define a DataProcessor interface that can process individual items or arrays of items. The StringToNumberProcessor class implements this interface to convert strings to numbers. This pattern is incredibly useful when you need to process data in various ways while maintaining type safety.

Conclusion

Congratulations! You've just taken a big step in your TypeScript journey by learning about generic interfaces. These powerful tools allow us to write flexible, reusable code that can work with different types of data. Remember, practice makes perfect, so don't be afraid to experiment with these concepts in your own projects.

Here's a quick reference table of the methods we've covered:

Method Description
interface Box<T> Creates a generic interface for a box that can hold any type
interface Pair<T, U> Creates a generic interface for a pair of items of different types
interface Reversible<T> Creates a generic interface with a method to reverse an array
interface Transformer<T, U> Creates a generic interface for a function that transforms one type to another
interface DataProcessor<T, U> Creates a generic interface for processing individual items or arrays of items

Keep coding, keep learning, and remember – in the world of TypeScript, generics are your superpower! ?‍♀️?‍♂️

Credits: Image by storyset