Generics are building components in TypeScript that are reusable and capable of working with multiple types of data rather than just one. They provide a way to create flexible code by allowing functions, classes, or interfaces to use any data type when they are called.
Let’s look at an example to help you understand. This may now be accomplished by defining an identity
function of type any
.
function identity(arg: any): any {return 101;}let str = identity("How you doin");console.log(typeof str); // displays the type of outputconsole.log(str); // displays the output
The above method does fulfill the requirement of getting any data in the function. However, it does not keep track of the data type that was passed to the function in the first place and allows a different return type. It can be observed in the code above that we are passing a string and returning a number. A generic component, however, does not allow this type of behavior. It only returns the type that was passed to it as input.
For generics, we use a special kind of variable Type
that can take in any data type. Let’s go through different types of generics in TypeScript:
A generic function can accept any type and return the same type. To further comprehend this notion, consider the following code sample.
function identity<Type>(arg: Type): Type {return arg;}let str = identity<string>("Hello");console.log(typeof str);console.log(str); // Output: Hellolet num = identity<number>(42);console.log(typeof num);console.log(num); // Output: 42
Here’s a brief overview of the code above:
Lines 1–3: This is a generic function named identity
. It takes in and returns a single argument of type Type
. The <Type>
type indicates that identity
function could work with any type of data that is input to the function.
Lines 5–7: This is a call to identity
function with string
type data. After that, the type of the data and data itself is printed.
Lines 9–11: This is a call to identity
function with number
type data. After that, the type of the data and data itself is printed.
Note: If you replace the
arg
in line 2, with a number, it will return an error because the input type is a string, and we are returning a number.
A generic class can work with any specific data type. To further comprehend this notion, consider the following code sample:
class StackOperations<Type> {private stack: Type[];constructor() {this.stack = [];}appendItem(item: Type): void {this.stack.push(item);}deleteItem(): Type[] {this.stack.pop();return this.stack;}getStack(): Type[] {return this.stack;}}let stringStack = new StackOperations<string>();stringStack.appendItem("Red");stringStack.appendItem("Blue");stringStack.appendItem("Green");console.log(stringStack.getStack());stringStack.deleteItem();console.log(stringStack.getStack());let numberStack = new StackOperations<number>();numberStack.appendItem(1);numberStack.appendItem(2);numberStack.appendItem(3);console.log(numberStack.getStack());numberStack.deleteItem();console.log(numberStack.getStack());
Here’s a brief overview of the code above:
Lines 1–19: This is a generic class StackOperations
with a private property stack
, and methods constructor()
, appendItem()
, deleteItem()
, and getStack()
. These methods are used to initialize the stack
property, add an item at its end, delete the last item, and get the stack
respectively.
Lines 23–29: A new string
type instance of StackOperations
class is created, and three strings are appended to it, then the stack is displayed, followed by the deletion of the last appended string in the stack. At last, the stack is displayed again.
Lines 32–38: A new number
type instance of StackOperations
class is created, and three numbers are appended to it, then the stack is displayed, followed by the deletion of the last appended number in the stack. At last, the stack is displayed again.
We can define generic interfaces that can work with different data types. We create instances of different specific types that can use the interface. For example, instances of a generic Types
can hold different types of values. To further comprehend this notion, consider the following code sample:
interface Types<Type> {value: Type;}const text: Types<string> = {value: "How you doin!"};const numbers: Types<number> = {value: 101};const booleans: Types<boolean> = {value: false};console.log(text.value);console.log(numbers.value);console.log(booleans.value);
Here’s a brief overview of the code above:
Lines 1–3: An interface Types
that can hold any type of data.
Lines 5–15: Three instances of Types
interface that can hold string
, number
, and boolean
data types.
Lines 17–19: Display all the instances.
Generics are type-safe because they preserve the type data at compile-time. They are a type of function that can adapt to different data types without having to check the type of data.
Free Resources