Skip to main content

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.