Generics are a powerful feature in TypeScript that allow you to create reusable components and functions that can work with different types without losing type safety. In this section, we will explore how to use generics with classes in TypeScript.
Generics enable you to write flexible and reusable code by allowing parameters for types. This means you can define a class or function that operates on a variety of types while still maintaining type safety. Generics are denoted by angle brackets (<>) and are used to specify the type parameter(s) of a generic class.
To create a generic class, you need to declare one or more type parameters within angle brackets after the class name. These type parameters can then be used throughout the class definition.
Let's define a generic class called Box that can hold any type of value:
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getContent(): T {
return this.content;
}
setContent(value: T): void {
this.content = value;
}
}
In this example, T is a type parameter that represents the type of the content stored in the box. The constructor and methods use this type parameter to ensure type safety.
You can create instances of the Box class with different types:
const stringBox = new Box<string>('Hello, world!');
console.log(stringBox.getContent()); // Output: Hello, world!
const numberBox = new Box<number>(42);
console.log(numberBox.getContent()); // Output: 42
const booleanBox = new Box<boolean>(true);
console.log(booleanBox.getContent()); // Output: true
Each instance of the Box class is type-safe and can only store values of the specified type.
You can also define generic classes with multiple type parameters. This is useful when you need to work with more than one type within the class.
Let's create a generic class called Pair that holds two related values of different types:
class Pair<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;
}
}
In this example, K and V are type parameters representing the types of the key and value, respectively.
You can create instances of the Pair class with different types for the key and value:
const stringNumberPair = new Pair<string, number>('age', 25);
console.log(stringNumberPair.getKey()); // Output: age
console.log(stringNumberPair.getValue()); // Output: 25
const booleanStringPair = new Pair<boolean, string>(true, 'yes');
console.log(booleanStringPair.getKey()); // Output: true
console.log(booleanStringPair.getValue()); // Output: yes
Type constraints allow you to restrict the types that can be used with a generic class. This is useful when you need to ensure that certain operations are valid for the type parameter.
Let's create a generic class called Lengthy that only works with types that have a length property:
class Lengthy<T extends { length: number }> {
private item: T;
constructor(item: T) {
this.item = item;
}
getLength(): number {
return this.item.length;
}
}
In this example, the type parameter T is constrained to types that have a length property.
You can create instances of the Lengthy class with types that have a length property:
const stringLengthy = new Lengthy<string>('Hello');
console.log(stringLengthy.getLength()); // Output: 5
const arrayLengthy = new Lengthy<number[]>([1, 2, 3]);
console.log(arrayLengthy.getLength()); // Output: 3
TypeScript allows you to specify default types for type parameters. This is useful when you want to provide a sensible default type that can be overridden if needed.
Let's create a generic class called Container with a default type parameter:
class Container<T = string> {
private items: T[];
constructor() {
this.items = [];
}
addItem(item: T): void {
this.items.push(item);
}
getItems(): T[] {
return this.items;
}
}
In this example, the type parameter T has a default value of string.
You can create instances of the Container class with or without specifying the type:
const stringContainer = new Container<string>();
stringContainer.addItem('Hello');
console.log(stringContainer.getItems()); // Output: ['Hello']
const numberContainer = new Container<number>();
numberContainer.addItem(42);
console.log(numberContainer.getItems()); // Output: [42]
const defaultContainer = new Container();
defaultContainer.addItem('Default');
console.log(defaultContainer.getItems()); // Output: ['Default']
Generics are a powerful feature in TypeScript that allow you to create flexible and reusable classes. By using type parameters, multiple type parameters, type constraints, and default types, you can write code that is both generic and type-safe. Understanding how to use generics effectively will help you write more robust and maintainable TypeScript applications.
By following the examples and best practices outlined in this tutorial, you should be well-equipped to use generics with classes in your TypeScript projects.