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.
// 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
// 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:
- Creating generic utility functions (e.g., sorting arrays of any type)
- Building reusable components for UI frameworks
- 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.