advanced-generics
Advanced Generics in TypeScript:
- Generics Provide Flexibility:
Generics act as placeholders for types. They let you create functions, classes, and interfaces that can operate on any type, which is determined when you use the generic.
Example:
function identity<T>(arg: T): T {
return arg;
}
- Type Parameters Capture Type Information:
Type parameters in generics capture and use the type information when the function, class, or interface is used, ensuring the integrity of the type throughout the code.
Example:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
- Constraints Limit Type Flexibility:
Constraints in generics limit the types that can be used, ensuring they have certain properties or methods.
Example:
function logProperty<T extends { prop: string }>(obj: T): string {
return obj.prop; // This will not compile if prop does not exist
}
- Extending Types with Constraints:
Use extends
to ensure a type parameter has certain properties.
Example:
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property
return arg;
}
- Using Class Types in Generics:
You can ensure a generic will work with certain class instances by using class types.
Example:
class BeeKeeper {
hasMask: boolean = true;
}
class ZooKeeper {
nametag: string = 'Mikko';
}
class Animal {
numLegs: number = 4;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
// This function can now be used to create instances of Animal or its subclasses.
- Default Type Parameters:
Provide default types for generics to be used if no specific type is provided.
Example:
function createArray<T = string>(length: number, value: T): T[] {
return new Array(length).fill(value);
}
- Multiple Type Parameters:
Generics can have multiple type parameters for more complex type relationships.
Example:
function merge<U, V>(obj1: U, obj2: V): U & V {
return { ...obj1, ...obj2 };
}
In these examples, you can see how generics can be used to create flexible and type-safe code that can adapt to a wide range of scenarios.
- Generic Constraints and Index Types:
You can enforce a type parameter to have a certain property, which is especially useful when you want to pick a set of properties from a type.
Example:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // value has the type of T[K]
}
- Conditional Types in Generics:
Conditional types allow you to define a type that can change based on the conditions applied to generics, similar to if statements at the type level.
Example:
type Check<T> = T extends string ? 'Text' : 'Other';
- Mapped Types with Generics:
Mapped types with generics enable you to apply transformations to all properties of a given type.
Example:
type ReadOnly<T> = { readonly [P in keyof T]: T[P] };
- Utility Types and Generics:
Utility types utilize generics to apply transformations, such as making all properties optional or read-only.
Example:
type PartialPoint = Partial<{ x: number; y: number }>;
- Generic Type Aliases:
Type aliases can be generic and combined with other features like union or intersection types to create complex, maintainable types.
Example:
type Container<T> = { value: T } | { error: string };
- Function Overloads with Generics:
Function overloads allow multiple function signatures to work with different types, enhancing type inference and usability.
Example:
function makeArray<T>(item: T): T[];
function makeArray<T>(items: T[], count: number): T[];
function makeArray<T>(itemOrArray: T | T[], count?: number): T[] {
if (count != null) {
return new Array(count).fill(itemOrArray as T);
}
return [itemOrArray as T];
}
- Generic Utility Functions:
Creating generic utility functions can help in working with any type, reducing redundancy.
Example:
function logItem<T>(item: T): void {
console.log(item);
}
- Using Type Parameters in Type Guards:
Type parameters in type guards can be used to create more precise and reusable checks in your application.
Example:
function isString<T>(arg: T | string): arg is string {
return typeof arg === "string";
}
In these examples, you can see how generics enhance type safety and flexibility, allowing you to create highly reusable and adaptable code structures.