In TypeScript, you can use generic classes to create reusable classes that work with different types. They provide a way to define a class that can operate with various data types, ensuring type safety. Generic classes resemble generic functions or interfaces but are applicable to classes instead. In this topic, you will learn about generic classes and how to use them.
What are generic classes?
Generic classes allow for the introduction of type parameters to classes. This ability enables them to handle varying data types without constantly needing to cast or assert types. These type parameters serve as placeholders for the real data types that you would use when making class instances.
To define a generic class in TypeScript, use the angle bracket notation <T> right after the class name, where T symbolizes a type parameter. This type parameter can then come into use within the class to point out the type of specific properties, method parameters, or return values.
Here's an example of a generic class named Container. This class represents a simple container that can accommodate an item of any type:
class Container<T> {
private item: T;
constructor(item: T) {
this.item = item;
}
getItem(): T {
return this.item;
}
setItem(newItem: T) {
this.item = newItem;
}
}In this instance, Container<T> outlines a generic class named Container capable of housing instances of any type T. Inside the class, the type parameter T comes into play to guarantee type safety for the item property as well as the getItem and setItem methods.
Using generic classes
You use generic classes by creating instances with specific data types. Let's revisit the previous example:
const numberContainer = new Container<number>(10);
console.log(containerBox.getItem()); // Output: 10
const stringContainer = new Container<string>('Hello');
console.log(stringContainer.getItem()); // Output: HelloIn this code, numberContainer is an instance of Container with a type parameter of number, and stringContainer is also an instance of Container but with a type parameter of string. The type parameter helps the class maintain type safety, stopping you from assigning an incompatible type to the item property or inputting the wrong type to methods.
Generic classes have several benefits; they provide type safety, allow for code reusability, and add flexibility. They maintain type safety, preventing type-related errors and unexpected outcomes. They permit code to be reused for different data types, reducing code repetition and improving its maintainability. Also, they make your code more adaptable to various situations, as they can work with a range of data types without needing changes.
Examples of generic classes
Let's create a generic class called Stack<T>, representing a stack data structure:
class Stack<T> {
private items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // Output: 2
const stringStack = new Stack<string>();
stringStack.push("Hello");
stringStack.push("World");
console.log(stringStack.pop()); // Output: WorldWe created two stack instances—one for numbers and another for strings. The generic class lets the stack work with different types while keeping type safety.
The line private items: T[] = []; declares a private property items as an array of type T. It serves as the stack's underlying storage, where elements of type T are stored.
The method push(item: T) lets you add an item of type T to the stack's top. It takes an argument item of type T and pushes it onto the items array.
The method pop(): T | undefined removes and returns the item at the stack's top. If the stack is not empty, it returns an item of type T, or it returns undefined if the stack is empty.
We can also create a generic class called KeyValuePair<K, V>, representing a key-value pair:
class KeyValuePair<K, V> {
private key: K;
private value: V;
constructor(key: K, value: V) {
this.key = key;
this.value = value;
}
getKey(): K {
return this.key;
}
getValue(): V {
return this.value;
}
}
const pair = new KeyValuePair<string, number>("count", 42);
console.log(pair.getKey()); // Output: count
console.log(pair.getValue()); // Output: 42The line class KeyValuePair<K, V> declares the KeyValuePair class as a generic class with two type parameters: K for the key type and V for the value type. These parameter types represent the types of the key and value in the key-value pair. The getKey and getValue methods let us obtain the key and value pairs.
The line const pair = new KeyValuePair<string, number>("count", 42); creates an instance of the KeyValuePair class with type parameters string for the key type and number for the value type. It results in a key-value pair where the key is "count" and the value is 42.
Both examples demonstrate the usage of a generic class. They let the key and value to be of different types, providing flexibility and type safety.
Conclusion
Generic classes in TypeScript offer a method to develop reusable and type-secure code. You can write classes that function with multiple types, all while keeping type safety. In numerous situations, including data structures, collections, and adapters, generic classes prove helpful. By using type parameters, you can design more adaptable and reusable code for your TypeScript projects.