Upskills Community
Upskills
upskills.dev
Join our community on Discord to get help about the tutorials and ask your questions.
Upskills
@upskillsdev
Checkout our GitHub to find the source code and submit any issues or feature requests.
Generics is a core feature in statically-typed languages like TypeScript, enabling developers to define components, functions, or structures that can accept and enforce types passed during use. This enhances flexibility, promotes reusability, and reduces code duplication.
In this tutorial, we'll explore practical examples of TypeScript generics and learn how to use them effectively in functions, types, classes, and interfaces to improve type safety and adaptability in your code.
Prerequisites
class MyStorage<T> {private items: T[] = [];addItem(item: T): void {this.items.push(item);}getAllItems(): T[] {return this.items;}}// Usageconst stringStorage = new MyStorage<string>();stringStorage.addItem("Hello");stringStorage.addItem("World");const numberStorage = new MyStorage<number>();numberStorage.addItem(42);numberStorage.addItem(100);
Tutorial Content
6 sections • approximately 2 hours
Generics in TypeScript let developers create reusable and adaptable code while preserving strict type safety. By allowing types to be passed as parameters, generics help build structures and functions that can handle specific data types, without sacrificing the benefits of TypeScript’s static typing.
In TypeScript, generics are represented using angle brackets like <T>, where T stands for a passed-in type.
This notation works similarly to function parameters, acting as placeholders for a type that will be specified when the structure is instantiated.
1. Passing type parameter to type, interface, class
Type aliases can use generics to create flexible and reusable types.
type ApiResponse<T> = {// ^?data: T;status: number;error?: string;};// Usagetype User = { id: number; name: string };const userResponse: ApiResponse<User> = {data: { id: 1, name: "Alice" },status: 200,};type Company = { id: number; name: string; address: string };const companyResponse: ApiResponse<Company> = {data: { id: 1, name: "Alice", address: "Saigon, Vietnam" },status: 200,};
Interfaces can also take generic parameters, enabling more specific and reusable contracts.
interface KeyValuePair<K, V> {key: K;value: V;}// Usageconst stringPair: KeyValuePair<string, string> = { key: "name", value: "Alice" };const numberPair: KeyValuePair<number, boolean> = { key: 42, value: true };
Classes can take generic types to define properties and methods with flexible type handling.
class MyStorage<T> {private items: T[] = [];addItem(item: T): void {this.items.push(item);}getAllItems(): T[] {return this.items;}}// Usageconst stringStorage = new MyStorage<string>();stringStorage.addItem("Hello world");const stringItems = stringStorage.getAllItems();const numberStorage = new MyStorage<number>();numberStorage.addItem(42);const numberItems = numberStorage.getAllItems();
2. Passing type parameter to function
Functions can accept generic parameters to work with varying types dynamically.
function createKeyValueStore<K, V>() {return new Map<K, V>();}const userAges = createKeyValueStore<string, number>();// ^?userAges.set("Alice", 28);userAges.set("Bob", 35);// Generic syntax for arrow functionsconst createStoreWithInitialValue = <K, V>(key: K, value: V) => {const store = new Map<K, V>();store.set(key, value);return store;};// Notice that in this case we didn’t have to explicitly pass the type in the angle brackets (<>)// This behaviour is called "type parameter inference", the TS compiler just looked at the values of "key" and "value", and set Type to its type.const productPrices = createStoreWithInitialValue("Laptop", 1200);// ^?
3. Passing type parameter to React components
React components can also accept generic parameters to define the type of props they receive.
type DropdownProps<T> = {items: T[];getLabel: (item: T) => string;onSelect: (item: T) => void;};function Dropdown<T>({ items, getLabel, onSelect }: DropdownProps<T>) {return (<select onChange={(e) => onSelect(items[Number(e.target.value)])}>{items.map((item, index) => (<option key={index} value={index}>{getLabel(item)}</option>))}</select>);}// Usagetype Product = {id: number;name: string;}const ProductDropdown = () => {const products: Product[] = [{ id: 1, name: "Laptop" },{ id: 2, name: "Phone" },];return (<><Dropdown// ^?items={products}getLabel={(product) => product.name}onSelect={(product) => console.log("Selected:", product)}/>{/*You can also manually pass the explicit type to the Dropdown component just like what you do with normal functions*/}<Dropdown<string>// ^?items={['Apple', 'Banana', 'Orange']}getLabel={(item) => item}onSelect={(item) => console.log("Selected:", item)}/></>);};
In React, we don't usually need to pass the type explicitly to the component, because most of the time, the type can be inferred from the props that we pass to the component. And it's also a good practice to let the TS compiler infer the type for you, because it makes your code cleaner and more readable.
Section 1: What are generics in TypeScript?
About the author
NAB, Software Engineer
Hi, I’m Vu, a Software Engineer at NAB (National Australia Bank) with a love for creating web and mobile apps that don’t just look cool but feel great to use. I’ve had the chance to work with some awesome companies over the years, picking up new tricks and tackling many kinds of challenges along the way.
Now, I’m thrilled to share what I’ve learned and help others through fun, interactive coding tutorials!