DEV Community

Cover image for TypeScript Generics: Building Flexible and Reusable Components
Nahidul Islam
Nahidul Islam

Posted on

TypeScript Generics: Building Flexible and Reusable Components

TypeScript, a statically typed superset of JavaScript, has gained immense popularity among developers for its ability to catch errors at compile-time and improve code quality. One of TypeScript's most powerful features is generics, which allow you to write flexible, reusable code that works with multiple data types while maintaining type safety. In this article, we'll dive deep into TypeScript generics, exploring how they can help you create more robust and adaptable components and functions.

What are Generics?

When I first encountered generics, I thought of them as a way to create reusable code components that can work with different data types. Instead of specifying a particular type, we use a type parameter that acts as a placeholder. This allows us to write functions, classes, and interfaces that can operate on various data types while still providing type checking and intellisense support.

Why should we use Generics?

In my experience, there are several compelling reasons to use generics:

  1. Type Safety: Generics provide compile-time type checking, helping us catch errors early.
  2. Code Reusability: We can write once and use with multiple types.
  3. Flexibility: We can create components that work with various data structures.
  4. Better Intellisense: Our IDEs can provide better code completion and hints.

Basic Syntax

Let's start with a simple example to illustrate the syntax of generics:

function identity<T>(arg: T): T {
    return arg;
}

let output1 = identity<string>("Hello, Generics!");
let output2 = identity(42);

console.log(output1); // "Hello, Generics!"
console.log(output2); // 42
Enter fullscreen mode Exit fullscreen mode

In this example, we use <T> as a type parameter. When we call the function, we can either explicitly specify the type (as in output1) or let TypeScript infer it based on the argument (as in output2).

Practical Examples

Generic Functions

Let's look at a more practical example. Here's a function I often use to reverse an array of any type:

function reverseArray<T>(array: T[]): T[] {
    return array.reverse();
}

const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = reverseArray(numbers);
console.log(reversedNumbers); // [5, 4, 3, 2, 1]

const fruits = ["apple", "banana", "cherry"];
const reversedFruits = reverseArray(fruits);
console.log(reversedFruits); // ["cherry", "banana", "apple"]
Enter fullscreen mode Exit fullscreen mode

This reverseArray function works with arrays of any type, maintaining type safety throughout.

Generic Interfaces

I've found that generics are not limited to functions. We can also use them with interfaces to create flexible data structures:

interface KeyValuePair<TKey, TValue> {
    key: TKey;
    value: TValue;
}

let pair1: KeyValuePair<string, number> = { key: "age", value: 30 };
let pair2: KeyValuePair<number, boolean> = { key: 1, value: true };
Enter fullscreen mode Exit fullscreen mode

This KeyValuePair interface can be used with different types for both the key and the value.

Generic Classes

In my projects, I've found generics particularly useful when creating reusable class components. Here's an example of a generic Stack class I often use:

class Stack<T> {
    private items: T[] = [];

    push(item: T): void {
        this.items.push(item);
    }

    pop(): T | undefined {
        return this.items.pop();
    }

    peek(): T | undefined {
        return this.items[this.items.length - 1];
    }

    isEmpty(): boolean {
        return this.items.length === 0;
    }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);

console.log(numberStack.pop()); // 3
console.log(numberStack.peek()); // 2

const stringStack = new Stack<string>();
stringStack.push("Hello");
stringStack.push("World");

console.log(stringStack.pop()); // "World"
console.log(stringStack.isEmpty()); // false
Enter fullscreen mode Exit fullscreen mode

This Stack class can be used with any data type, providing type-safe operations for pushing, popping, and peeking at elements.

Advanced Generic Techniques

Constraints

Sometimes we want to restrict the types that can be used with our generic components. We can do this using constraints:

interface Lengthwise {
    length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
    console.log(arg.length);
}

logLength("Hello"); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(3); // Error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'
Enter fullscreen mode Exit fullscreen mode

In this example, T extends Lengthwise ensures that the argument passed to logLength has a length property.

Generic Type Aliases

I've found that type aliases can also leverage generics:

type Nullable<T> = T | null;
type Pair<T> = [T, T];

let nullableNumber: Nullable<number> = 5;
nullableNumber = null;

let coordinates: Pair<number> = [10, 20];
Enter fullscreen mode Exit fullscreen mode

Conclusion

Generics are a fantastic feature of TypeScript that help us write code that is both flexible and type-safe. By leveraging generics, we can build reusable components and functions that work with various types while ensuring type consistency. We hope this guide gives us a solid understanding of how to use generics effectively in our TypeScript projects.

Let’s experiment with generics in our own code and see how they can make our development process smoother and more efficient!

Drop me an email here: nahidul7562@gmail.com

Follow me on: πŸ™‹πŸ»β€β™‚οΈ

Explore my portfolio

Welcome to my professional portfolio websiteβ€”a curated glimpse into my professional world. Here, you'll find:

🌟 A collection of standout projects highlighting my expertise
πŸš€ Insights into my career trajectory and key achievements
πŸ’Ό A showcase of my diverse skills and competencies

Whether you're seeking inspiration, exploring collaboration opportunities, or simply curious about my work, I invite you to peruse my portfolio.

Your visit could be the first step towards a valuable professional connection. 🀝 Thank you for your interestβ€”I look forward to the possibilities our interaction might bring. 😊

Top comments (0)