TypeScript Course EP10: Generic Types
Video
Watch the full video on YouTube:
Introduction
In this tenth chapter of our TypeScript series, we introduce generic types. Generics are one of the most powerful features in TypeScript, allowing you to create reusable and flexible code that works with multiple data types without losing type safety.
A generic can be visualized as a box that collects something, but only knows it collects something, without knowing specifically what that something is until you start putting things in it.
Generic Types
Basic Definition
To create a generic type, we use angle brackets <T> where T is a representation of a type that hasn’t been defined yet:
export type Box<T> = {
content: T;
};
T is the common convention, but you can name it anything. The important thing is that it represents a type that only exists within the context of this type.
Using a Generic Type
Once defined, you can specify the type when using it:
// Box with a single number
export const aBoxOfNumber: Box<number> = {
content: 123,
};
// Box of numbers (array)
export const boxOfNumbers: Box<number[]> = {
content: [1, 2, 3],
};
TypeScript infers the type T based on what you pass. If you try to pass something that doesn’t match, it throws an error.
Generics with Complex Types
Generics aren’t limited to primitive types. You can use them with classes and interfaces:
type Pet = {
id: number;
name: string;
owner: string;
};
export const boxOfPets: Box<Pet[]> = {
content: [
{ id: 1, name: "Milo", owner: "Angel" }
],
};
Generic Functions
Generics also work with functions. This is useful when you need a function to work with multiple types while maintaining type safety.
Example: Environment Variables
Imagine you have an object with environment variables and want to create a function that accesses its values:
const variables = {
API_KEY: "some-string",
PORT: 3000,
DSN: "dsn-string",
IS_DEV: true,
};
A first approach without generics:
export function getEnvVar(key: string) {
return variables[key as keyof typeof variables];
}
This approach has problems: TypeScript doesn’t know exactly what type it’s returning.
Generic Function with Constraints
The solution is to use generics with extends to restrict what values the function can receive:
export function getEnvVar<T extends keyof typeof variables>(key: T) {
return variables[key];
}
How Constraints Work
T extends keyof typeof variables: The genericTmust be one of the keys of thevariablesobject- When passing
"API_KEY", TypeScript knows it returnsstring - When passing
"PORT", TypeScript knows it returnsnumber - When passing
"IS_DEV", TypeScript knows it returnsboolean
const apiKey = getEnvVar("API_KEY"); // type: string
const port = getEnvVar("PORT"); // type: number
const isDev = getEnvVar("IS_DEV"); // type: boolean
TypeScript automatically infers the return type based on the key you pass.
Generics in JavaScript Classes
If you’ve worked with JavaScript before, you’ve probably seen generics in action without knowing it:
// Array generic
const arrayOfPets: Array<Pet> = [];
// Map generic
const mapOfPets = new Map<string, Pet>();
// Set generic
const setOfPets = new Set<Pet>();
These JavaScript classes use generics internally to define the type of elements they contain.
Generics with Default Values
You can define a default type for a generic using the syntax T = Type:
export type Box<T = string> = {
content: T;
};
// If you don't define the type, uses string by default
export const boxOfStrings: Box = {
content: "hello",
};
// But you can override the type
export const boxOfNumbers: Box<number> = {
content: 42,
};
Multiple Generics with Optionals
If a type has multiple generics and the first is optional, the second must also be optional:
export type Box<T = string, Meta = null> = {
content: T;
meta: Meta;
};
// Uses default values
export const boxWithDefaults: Box = {
content: "hello",
meta: null,
};
// Define both types
export const boxWithMeta: Box<string, string> = {
content: "hello",
meta: "metadata",
};
Best Practices
- Use
Tas the convention for generic names, but you can use more descriptive names - Apply constraints with
extendsto limit what types a generic can receive - Use generics with default values to make your API more flexible
- Generics with
keyofallow you to create functions that operate on an object’s keys - Prefer generics over
anyto maintain type safety
Conclusion
Generic types are a fundamental tool in TypeScript that allows you to create reusable and flexible code. From simple types like Box<T> to functions with complex constraints, generics are the foundation for building robust and maintainable architectures.
Repository
💻 Repository: https://github.com/radagva/ts-example
Next Steps
🔔 Subscribe to the channel to not miss the next chapters of the TypeScript course!
