Generics in TypeScript provide a way to create reusable components that can work with any data type. They allow you to define functions, classes, and interfaces that work with types specified by the caller, ensuring type safety while providing the flexibility to use different types as needed.
Key Concepts of Generics
Type Variables Generics use type variables to denote types that are specified when the generic is instantiated. The most commonly used type variable is T
.
Generic Functions Functions can be made generic to operate on a variety of types while maintaining type safety.
Generic Classes Classes can be defined with generics to handle different types of data in a type-safe manner.
Generic Interfaces Interfaces can use generics to define structures that can work with multiple types.
Generic Functions
A generic function can accept arguments of any type and return a value of that same type.
Example
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // Explicit type
let output2 = identity<number>(100); // Explicit type
let output3 = identity("myString"); // Type inference
Generic Classes
A generic class can operate on various data types while ensuring type safety.
Example
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zeroValue: T, addFn: (x: T, y: T) => T) {
this.zeroValue = zeroValue;
this.add = addFn;
}
}
let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);
console.log(myGenericNumber.add(5, 10)); // Output: 15
Generic Interfaces
Interfaces can use generics to describe a structure that can work with multiple types.
Example
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
console.log(myIdentity(5)); // Output: 5
Using Multiple Type Variables
Generics can use multiple type variables to create more complex and flexible components.
Example
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
let mergedObj = merge({ name: "Alice" }, { age: 25 });
console.log(mergedObj); // Output: { name: "Alice", age: 25 }
Generic Constraints
Sometimes you want to limit the kinds of types that can be passed as type arguments. You can use constraints to achieve this.
Example
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no error
return arg;
}
loggingIdentity({ length: 10, value: 3 });
Default Type Parameters
You can provide default types for generics to make the generic components more flexible and easier to use.
Example:
function createArray<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
let stringArray = createArray(3, "x"); // Output: ["x", "x", "x"]
let numberArray = createArray<number>(3, 5); // Output: [5, 5, 5]