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!
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 aBox
that holds a number. -
stringBox
is aBox
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