Generics in TypeScript allow you to write code that is not tied to a specific data type. Instead, you can create a generic type that can be used with any data type, making your code more efficient and easier to maintain. TypeScript generics are similar to C# and Java generics, and they provide a way to create reusable code that is both type-safe and flexible.
- 🚗 Vehicle fleet management
A vehicle fleet management system can use TypeScript generics to work with any type of vehicle
class Vehicle<T> {
make: string;
model: string;
year: number;
data: T;
constructor(make: string, model: string, year: number, data: T) {
this.make = make;
this.model = model;
this.year = year;
this.data = data;
}
}
- 🎲 Game dice
A dice in a game can have multiple sides, and we can use TypeScript generics to represent the possible values of each side and the number of sides
class Dice<Sides extends number, Value> {
sides: Sides;
constructor(sides: Sides) {
this.sides = sides;
}
roll(): Value {
return Math.floor(Math.random() * this.sides) as Value;
}
}
const dice = new Dice<6, string>(6);
console.log(dice.roll());
// returns a random string value between 0 and 5
- 🧬 DNA sequence
A DNA sequence can be represented as a string of nucleotides (A, C, G, T). We can use TypeScript generics to create a DNA class that can work with any sequence of nucleotides and a name
type Nucleotide = "A" | "C" | "G" | "T";
class DNA<Sequence extends Nucleotide[], Name extends string> {
sequence: Sequence;
name: Name;
constructor(sequence: Sequence, name: Name) {
this.sequence = sequence;
this.name = name;
}
toString(): string {
return `${this.name}: ${this.sequence.join("")}`;
}
}
const dna = new DNA(['A', 'C', 'G', 'T'], 'DNA Sequence');
console.log(dna.toString()); // returns "DNA Sequence: ACGT"
- 🍝 Recipe
Let's say we want to create a recipe class that can hold any type of ingredient (meat, vegetables, fruits, etc.). We can use generics to create a recipe class that can work with any type of ingredient:
class Recipe<Ingredient> {
ingredients: Ingredient[];
constructor(ingredients: Ingredient[] = []) {
this.ingredients = ingredients;
}
add(ingredient: Ingredient) {
this.ingredients.push(ingredient);
}
remove(ingredient: Ingredient) {
const index = this.ingredients.indexOf(ingredient);
if (index !== -1) {
this.ingredients.splice(index, 1);
}
}
}
This allows us to create a recipe that can hold any type of ingredient:
interface Meat {
name: string;
cut: string;
}
interface Vegetable {
name: string;
color: string;
}
const recipe = new Recipe<Meat | Vegetable>([
{ name: 'Beef', cut: 'Chuck' },
{ name: 'Carrots', color: 'Orange' },
]);
recipe.add({ name: 'Chicken', cut: 'Breast' });
recipe.add({ name: 'Spinach', color: 'Green' });
console.log(recipe.ingredients);
// returns an array of meats and vegetables
- 📝 Form validation
Let's say we want to create a form validation function that can handle multiple types of form fields (text fields, checkboxes, radio buttons, etc.)
type FormField<T> = {
value: T;
required: boolean;
validator?: (value: T) => boolean;
};
function validateForm<T>(fields: FormField<T>[]): boolean {
return fields.every(field => {
if (field.required && !field.value) {
return false;
}
if (field.validator && !field.validator(field.value)) {
return false;
}
return true;
});
}
const textFields: FormField<string>[] = [
{ value: 'John Doe', required: true },
{ value: 'johndoe@example.com', required: true, validator: value => value.includes('@') },
];
const checkboxFields: FormField<boolean>[] = [
{ value: true, required: true },
{ value: false, required: true },
];
console.log(validateForm(textFields)); // returns true
console.log(validateForm(checkboxFields)); // returns false
- 🔍 Object key lookup
Let's say we want to create a function that can fetch a value from an object using multiple keys of different types. We can use generics to create a function that can work with multiple type arguments to fetch the value from the object
type FetchValue<T, K extends readonly string[]> =
K extends [infer First, ...infer Rest]
? First extends keyof T
? Rest extends []
? T[First]
: FetchValue<T[First], Rest>
: never
: never;
type User = {
id: number;
name: string;
address: {
street: string;
city: string;
};
};
const user: User = {
id: 1,
name: "John",
address: {
street: "123 Main St",
city: "Anytown"
}
};
// Fetch the type of user.name
type UserName = FetchValue<User, ["name"]>; // string
// Fetch the type of user.address
type UserCity = FetchValue<User, ["address"]>; // { street: string, city: string }
// Fetch the type of user.address.city
type UserCity = FetchValue<User, ["address", "city"]>; // string
Top comments (0)