Generics in TypeScript
Generics play a crucial role in programming, as they enable creating type-safe functions without specifying the exact type beforehand but allowing constraints and checks on the programmer’s types. This article introduces the concept of generics, lists their advantages, and shows how to use them.
Generics in TypeScript allow developers to write reusable and flexible code by abstracting over types. Using generics, developers can create functions, classes, and interfaces that work with any type rather than being limited to a specific type. The ability to create a component that can operate over several types rather than just one is one of the main tools in the toolbox for creating reusable elements in programming languages such as C# and Java. As a result, users can use different types while consuming these components.
This article introduces the idea of Generics in TypeScript and when to use them. It describes how generics can be used with functions, classes, interfaces, and types and gives examples of their use in real-world situations. These examples are clearly defined so you can follow them in your preferred integrated development environment.
Advantages of Generics
The list of advantages that generics offer in TypeScript is as follows:
- We can securely store one object using generics without storing the other types.
- Using generics, we can avoid having to typecast any variables or functions at the time of calling.
- Generics are typically checked at compile time to ensure no problems at runtime.
- Generics can help improve code performance by allowing TypeScript to optimize it for specific types of data, reducing the type-checking needed at runtime.
- Using generics makes code more readable and understandable, making it easier for others to work with and maintain.
Using Generics in Functions
Using generics in functions, you can create code that can handle various data types. And also makes your code more flexible and reusable, as it can be applied to different input types without requiring individual functions for each type. As a result, your code becomes easier to maintain and comprehend.
Developers define a placeholder type within angle brackets, such as <T>
, and use that placeholder type within the function to implement generics.
The following is an example of how to write the function that returns the first element of an array
:
Here’s an example of a function that returns the first element of an array in TypeScript using generics:
function firstelement<T>(arr: T[]): T {
return arr[0];
}
To use the function with different types of arrays, the specific type is passed when the function is called. For example:
function firstElement<T>(array: T[]): T | undefined {
return array[0];
}
const numbers = [1, 2, 3];
const firstNumber = firstElement<number>(numbers);
console.log(firstNumber); // 1
const names = ["Daniel", "Micheal", "Charlie"];
const firstName = firstElement<string>(names);
console.log(firstName); // 'Daniel'
In this example, the function is used with two arrays of different types, numbers (an array of numbers) and names (an array of strings). By bypassing the specific type when the function is called, the function can work with the appropriate data type.
Using Generics in Classes and Interfaces
Generics can also be used in classes and interfaces. For example, a class representing a stack can be written with a generic type to support any data type.
class Stack<T> {
private data: T[] = [];
push(item: T) {
this.data.push(item);
}
pop(): T | undefined {
return this.data.pop();
}
}
let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2
console.log(numberStack.pop()); // 1
let stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.pop()); // 'b'
console.log(stringStack.pop()); // 'a'
To use the class with different data types, the specific type is passed when the class is instantiated. For example:
let numbers = new Stack<number>();
numbers.push(1);
numbers.push(2);
console.log(numbers.pop()); // 2
console.log(numbers.pop()); // 1
let names = new Stack<string>();
names.push("Alice");
names.push("Bob");
console.log(names.pop()); // 'Bob'
console.log(names.pop()); // 'Alice'
In this example, the class is used to create two stacks of different types, numbers (a stack of numbers) and names (a stack of strings). The class can work with the appropriate data type by passing the specific type when the class is instantiated.
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
Built-in Generic Type and Interfaces
TypeScript provides several built-in generic types and interfaces, such as Array
, Promise
, and Map
, commonly used in JavaScript. These types are defined with a generic type and can be used with any data type.
For example, Array
is a generic type representing an ordered element collection. It can be used with any data type, such as numbers or strings, bypassing the specific type when the Array is created.
let numbers = [1, 2, 3];
let names = ['Daniel', 'Micheal', 'Charlie'];
Another example is the Promise type, which represents a value that may not be available yet but will be at some point in the future. The Promise type is also defined with a generic type, representing the value that will be available in the future.
let promise: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Hello, World!");
}, 1000);
});
promise.then((value: string) => console.log(value)); // 'Hello, World!'
Finally, Map
is a generic type representing a collection of key-value pairs. The generic types for the keys and values can be different.
let map = new Map<string, number>();
map.set("Daniel", 25);
map.set("Michael", 30);
console.log(map.get("Daniel")); // 25
In addition to using these built-in generic types and interfaces, you can also extend them to add additional functionality. For example, you could create a custom Stack class that extends the built-in Array type, as shown in the previous example. This allows us to take advantage of the built-in functionality of the Array type while adding your own custom functionality.
Conclusion
Generics in TypeScript provide an essential feature for writing reusable and flexible code. They allow developers to be abstract over types, making it possible to write functions, classes, and interfaces that work with any data type. This feature of TypeScript makes the code more readable and maintainable and reduces the amount of duplicated code.