Introduction to TypeScript Generics
TypeScript is a strictly typed programming language that adds static typing to JavaScript. One of the most powerful and often underestimated features of TypeScript is Generics. If you have ever written a function that needs to work with different data types, or created a component that needs to be universal, then Generics are exactly what you need.
In this article, we will thoroughly examine what generics in TypeScript are, why they are needed, and how to use them in practice. You will learn how to make your code more flexible, safe, and reusable.
What are Generics and why are they needed?
Generics are a mechanism that allows you to create components (functions, classes, interfaces) that work with various data types without losing type information. Imagine you are writing a function that returns the first element of an array. Without generics, you would have to use the any type, which deprives you of the benefits of strict typing.
Why use Generics?
- Type Safety: TypeScript checks types at compile time, preventing errors.
- Code Reusability: One function or class can work with any data types.
- Improved Readability: Code becomes more understandable and self-documenting.
- Reduced Duplication: You don't need to write separate functions for each data type.
Without Generics, you would write something like this:
// Bad: we lose type informationfunction getFirstElement(arr: any[]): any { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Type anyconsole.log(num.toUpperCase()); // Runtime error!With Generics, the code becomes safe and flexible:
// Good: we preserve the typefunction getFirstElement<T>(arr: T[]): T | undefined { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Type numberconst str = getFirstElement(['a', 'b', 'c']); // Type string
// num.toUpperCase(); // Compilation error! number does not have method toUpperCaseGenerics Syntax Basics
Angle Bracket Syntax
Generics use angle brackets <> to specify type parameters. Parameters are usually denoted by a single letter: T (Type), U, V, etc. However, you can use any meaningful names.
// Simplest example: a function with one type parameterfunction identity<T>(arg: T): T { return arg;}
// Explicit type specificationlet result1 = identity<string>('Hello');
// Type inference (TypeScript determines the type itself)let result2 = identity(42); // Type numberMultiple Type Parameters
You can use several type parameters in one function or class:
function merge<T, U>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 };}
const merged = merge({ name: 'John' }, { age: 30 });// Type: { name: string } & { age: number }console.log(merged.name); // Johnconsole.log(merged.age); // 30Constraints
Sometimes you need to restrict the types that can be passed to a generic. The extends keyword is used for this:
interface HasLength { length: number;}
// The function only works with types that have a length propertyfunction logLength<T extends HasLength>(arg: T): T { console.log(arg.length); return arg;}
logLength('Hello'); // Works: 5logLength([1, 2, 3]); // Works: 3// logLength(123); // Error! number does not have length