Generics
Generics in TypeScript are a feature that allows you to write reusable code by passing a type as a parameter to another type, whether it’s a class, interface, or function. This means that you don’t always have to specify the type explicitly when writing code, as you can use Generics to work flexibly with various types without resorting to using ”: any.”
The main advantages of using Generics include:
- Code Reusability: You can use the same code with different types without rewriting it.
- Enhanced Safety: Generics help detect potential errors at compile time rather than runtime.
- Dealing with Multiple Types: Generics allow you to work with a variety of types without specifying a particular type.
Generics can be used in TypeScript to create:
- Generic Classes.
- Generic Functions.
- Generic Interfaces.
- Generic Methods.
// A generic function that returns the input value as is
function returnType<T>(val: T): T {
return val;
}
// Usage of the generic function with different types
const numValue: number = returnType<number>(100);
const strValue: string = returnType<string>("Elzero");
const boolValue: boolean = returnType<boolean>(true);
const arrValue: number[] = returnType<number[]>([1, 2, 3, 4]);
console.log(`Number Value: ${numValue}`);
console.log(`String Value: ${strValue}`);
console.log(`Boolean Value: ${boolValue}`);
console.log(`Array Value: ${arrValue}`);In this example:
- We define a generic function
returnType<T>that takes a value of typeTand returns the same value of typeT. - We demonstrate using the
returnTypefunction with various types, including numbers, strings, booleans, and arrays. - We specify the type parameter
Twhen calling thereturnTypefunction, allowing TypeScript to infer the correct types for the returned values. - The function provides type safety, ensuring that the returned value has the same type as the input value.
Generics Multiple Types
Generics in TypeScript can handle multiple types using union types or intersection types, allowing you to create flexible and versatile code that works with a variety of data types. Let’s break down these two approaches:
// Generic Function `returnTypeEx`
function returnTypeEx<T>(val: T): T {
return val;
}
console.log(returnTypeEx<number>(100)); // Returns: 100 (number)
console.log(returnTypeEx<string>("Elzero")); // Returns: "Elzero" (string)
// Arrow Function with Generics `returnTypeArrowSyntax`
const returnTypeArrowSyntax = <T>(val: T): T => val;
console.log(returnTypeArrowSyntax<number>(100)); // Returns: 100 (number)
console.log(returnTypeArrowSyntax<string>("Elzero")); // Returns: "Elzero" (string)
// Generic Function `testType`
function testType<T>(val: T): string {
return `The Value Is ${val} And Type Is ${typeof val}`;
}
console.log(testType<number>(100));
// Returns: "The Value Is 100 And Type Is number"
console.log(testType<string>("Elzero"));
// Returns: "The Value Is Elzero And Type Is string"
// Generic Function `multipleTypes`
function multipleTypes<T, S>(valueOne: T, valueTwo: S): string {
return `The First Value Is ${valueOne} And Second Value ${valueTwo}`;
}
console.log(multipleTypes<string, number>("Osama", 100));
// Returns: "The First Value Is Osama And Second Value 100"
console.log(multipleTypes<string, boolean>("Elzero", true));
// Returns: "The First Value Is Elzero And Second Value true"In this code:
- The
returnTypeExfunction and thereturnTypeArrowSyntaxarrow function are generic functions that return the input value as is. They accept a type parameter<T>and return a value of typeT. We demonstrate their usage with numbers and strings. - The
testTypefunction is a generic function that takes a value and returns a string describing the value and its type using thetypeofoperator. - The
multipleTypesfunction is a generic function that accepts two values of potentially different typesTandSand returns a string combining both values.
Each function demonstrates how generics can be used with different data types while maintaining type safety.
Generics Classes
Generics in classes allow you to create flexible and reusable class structures that can work with a variety of data types, enhancing code flexibility and type safety.
In this example, we’ll explore generics in classes using the User class as an example:
// Generic Class `User`
class User<T = string> {
constructor(public value: T) {}
// Method that takes a message of type `T` and displays it along with the `value` property
show(msg: T): void {
console.log(`${msg} - ${this.value}`);
}
}
// Creating an instance of `User` with a specific type parameter (string)
let userOne = new User<string>("Elzero");
console.log(userOne.value); // Outputs: "Elzero"
userOne.show("Message"); // Outputs: "Message - Elzero"
// Creating an instance of `User` with a type parameter that can be a number or a string
let userTwo = new User<number | string>(100);
console.log(userTwo.value); // Outputs: 100
userTwo.show("Message"); // Outputs: "Message - 100"
In this example:
- We define a generic class
User<T = string>, which allows us to create instances of theUserclass with different data types. The class takes a type parameterT, and by default, it is set tostringif no type is provided. - The constructor of the
Userclass accepts an initialvalueof typeT. - The
showmethod of theUserclass takes a message of typeTand displays it along with thevalueproperty. This method demonstrates how a generic class can work with the type specified by the type parameterT. - We create two instances of the
Userclass:userOnewith the type parameter explicitly set tostring, anduserTwowith the type parameter set tonumber | string. This showcases how the same class can be used with different data types while preserving type safety.
Generics And Interfaces
Generics in classes and interfaces allow us to create reusable and type-safe data structures that can work with different types, enhancing code flexibility and maintainability. In this example, we’ve demonstrated how the Collection class can be used with different item types while ensuring type safety.
In this example, we’ll explore the use of generics in classes and interfaces to create a flexible Collection class:
// Interface definitions for Book and Game
interface Book {
itemType: string;
title: string;
isbn: number;
}
interface Game {
itemType: string;
title: string;
style: string;
price: number;
}
// Generic Class `Collection`
class Collection<T> {
public data: T[] = [];
// Method to add an item of type `T` to the collection
add(item: T): void {
this.data.push(item);
}
}
// Creating an instance of `Collection` with type parameter `Book`
let itemOne = new Collection<Book>();
itemOne.add({ itemType: "Book", title: "Atomic Habits", isbn: 150510 });
itemOne.add({ itemType: "Book", title: "Follow Your Heart", isbn: 650650 });
console.log(itemOne);
// Creating an instance of `Collection` with type parameter `Game`
let itemTwo = new Collection<Game>();
itemTwo.add({ itemType: "Game", title: "Uncharted", style: "Action", price: 150 });
console.log(itemTwo);
In this code:
- We define two interfaces,
BookandGame, each representing a different type of item with specific properties. - We create a generic class
Collection<T>that allows us to create collections of items of typeT. Thedataproperty is an array of items, and theaddmethod allows us to add items of typeTto the collection. - We create two instances of the
Collectionclass:itemOneis an instance with the type parameter set toBook, so it can only store objects that conform to theBookinterface.itemTwois an instance with the type parameter set toGame, so it can only store objects that conform to theGameinterface.- We use the
addmethod to add items of the specified types to the collections.