How To Use Generics in TypeScript

TypeScript generics enable the creation of versatile and reusable code components by allowing functions, classes, and interfaces to work with a range of data types without sacrificing type safety. Using generics, developers can write flexible and parametric code that adapts to different data structures, enhancing the maintainability and adaptability of their software.

Whether applied to functions that operate on various types, classes that handle diverse data structures, or interfaces defining generic structures, TypeScript generics provide a powerful mechanism for writing code that is both concise and adaptable to a variety of scenarios.

Generic Functions

// A simple identity function using generics function identity<T>(arg: T): T { return arg; } // Usage let result = identity<string>("Hello, TypeScript!"); // 'result' is of type string

Generic Classes

// A generic class representing a pair of values class Pair<T, U> { constructor(public first: T, public second: U) {} } // Usage let pair = new Pair<number, string>(1, "two"); // 'pair' has the type Pair<number, string>

Running the TypeScript compiler (tsc) will generate the following JavaScript code:

// A generic class representing a pair of values class Pair { constructor(first, second) { this.first = first; this.second = second; } } // Usage let pair = new Pair(1, "two"); // 'pair' has the type Pair<number, string>

Generic Interfaces

// Define the User type interface User { id: number; name: string; // Other properties... } // A generic interface for a simple repository interface Repository<T> { getById(id: number): T; save(item: T): void; } // Implementation class UserRepository implements Repository<User> { getById(id: number): User { // Implementation here, return a mock user for illustration return { id, name: "John Doe" }; } save(user: User): void { // Implementation here } } // Usage const userRepository = new UserRepository(); const userById = userRepository.getById(1); userRepository.save({ id: 2, name: "Alice" });

Running the TypeScript compiler (tsc) will generate the following JavaScript code:

// Implementation class UserRepository { getById(id) { // Implementation here, return a mock user for illustration return { id, name: "John Doe" }; } save(user) { // Implementation here } } // Usage const userRepository = new UserRepository(); const userById = userRepository.getById(1); userRepository.save({ id: 2, name: "Alice" });

Generic Constraints

// A generic function with a constraint function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } // Usage let user = { id: 1, name: "John" }; let idValue = getProperty(user, "id"); // idValue is of type number let nameValue = getProperty(user, "name"); // nameValue is of type string

Running the TypeScript compiler (tsc) will generate the following JavaScript code:

// A generic function with a constraint function getProperty(obj, key) { return obj[key]; } // Usage let user = { id: 1, name: "John" }; let idValue = getProperty(user, "id"); // idValue is of type number let nameValue = getProperty(user, "name"); // nameValue is of type string

Default Generic Values

// A generic function with a default type function defaultValue<T = string>(arg: T): T { return arg; } // Usage let result = defaultValue(42); // result is of type number

Generic Utility Types

// Using Partial utility type with generics interface User { id: number; name: string; } function updateUser(user: User, updates: Partial<User>): User { return { ...user, ...updates }; } // Usage let initialUser: User = { id: 1, name: "John" }; let updatedUser = updateUser(initialUser, { name: "Doe" }); // 'updatedUser' has type User with optional properties

Running the TypeScript compiler (tsc) will generate the following JavaScript code:

function updateUser(user, updates) { return Object.assign(Object.assign({}, user), updates); } // Usage let initialUser = { id: 1, name: "John" }; let updatedUser = updateUser(initialUser, { name: "Doe" }); // 'updatedUser' has type User with optional properties

Advanced Use Cases

Generics can be used in complex scenarios like:

  1. Creating generic utility functions (e.g., sorting arrays of any type)
  2. Building reusable components for UI frameworks
  3. Defining type aliases for custom data structures

Conclusion

Generics in TypeScript enhance code reusability and maintainability by providing a way to write functions, classes, and interfaces that can work with different data types while preserving type safety. They are particularly useful in scenarios where the exact type is not known in advance, and the flexibility they offer can lead to more generic and versatile code.