TypeScript Generic Object: A Practical Guide

Updated: January 8, 2024 By: Guest Contributor Post a comment

Introduction

TypeScript generics are a tool that gives developers the power to create flexible and reusable components. By using generics, you can create data structures and functions that work with multiple data types while still maintaining the benefits of type safety.

Understanding Basics

Before diving into generics, it’s important to have a grasp of TypeScript’s type system. Types provide a way to describe the shape and behavior of an object and can include primitive types, enums, interfaces, classes, and more. Generics allow us to define types in a flexible manner, making them adaptable to a wide range of situations.

Let’s start with a simple generic function:

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

Here, the identity function takes any type T as an argument and returns that argument of the same type. You can call it using:

let output1 = identity<string>('myString');
let output2 = identity<number>(100);

Using Generics in Classes

Generics are also incredibly useful in defining classes, where they can ensure consistent typing across properties, methods, and constructors. Consider the following generic class:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

And this is how you can use this class:

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

Now, let’s apply this approach to an object with a more complex structure:

Implementing Interface Generics

Interfaces in TypeScript can also benefit from generics, serving as a contract for the shape of data. A generic interface can ensure that different data structures are treated consistently:

interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

Advanced Generic Patterns

As you become more accustomed to using generics, you will encounter advanced patterns like using multiple type variables, generic type constraints, and using generics with other TypeScript features. For instance, you can create a function with multiple type arguments:

function merge<U, V>(obj1: U, obj2: V): U & V {
    return { ...obj1, ...obj2 };
}

Generic type constraints allow you to specify a contract that the generic types must adhere to:

function prop<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

Conclusion

This tutorial provided a step into the powerful world of TypeScript generics. We’ve seen how generics can be used to create flexible components that work over a variety of types while ensuring the integrity of your code’s type safety. As you gain experience with TypeScript’s generics, optimize your application’s scalability and maintainability by embracing this robust feature.