Skip to content

Generic types

builtin generic types available in TypeScript (utility types)

  1. Array <T/>
  2. Promise <T>
  3. Partial <T> (partially applied type, make all properties optional)
  4. Omit <T, K> (omit properties, K is a list of properties to omit, returns a new type minus the properties in K)
  5. Readonly <T> (readonly properties, returns a new type with readonly properties)
  6. Required <T> (make all properties required, returns a new type with required properties)
  7. Record <Keys, Type>
  8. Pick <T, K> (returns a new type with only the properties in K)
  9. Exclude <UnionType, ExcludedMembers> (expects 2 union types, returns a new union type with all members of UnionType except for the members in ExcludedMembers)
  10. Extract<Type, Union> returns the a type that’s the intersection of Type and Union
  11. NonNullable <Type> returns a new type excluding null and undefined
  12. Parameters <Type> expects a function type and return a tuple type of its parameters
  13. ReturnType <Type> expects a function type and returns the return type of the function.
  14. ConstructorParameters <Type> expects a constructor function type and returns a tuple type of its parameters
  15. InstanceType <Type> expects a class type and returns a type of the instance that this class will produce.
  16. ThisParameterType <Type> extracts the type of this in a function type.
  17. OmitThisParameter <Type> removes this parameter from a function type.
  18. ThisType <Type>
  19. Uppercase <StringType>
  20. Lowercase <StringType>
  21. Capitalize <StringType>
  22. Uncapitalize <StringType

Custom generic types

function merge<T, U>(obj1: T, obj2: U): T & U {
    return Object.assign(obj1, obj2); // or as T & U
}

const merged = merge({ name: "Max" }, { age: 30 });
merged.name; // "Max"
merged.age; // 30
merged.isAdmin; // error, isAdmin is not defined on type T & U as {name: string} & {age: number}

Generic types constraints

  • you can specify constraints on a generic type, so that it can only be used with specific types.
  • you specify constraints on generic types by using the extends keyword.
// no constraint, problem
function merge<T, U>(obj1: T, obj2: U): T & U {
    return Object.assign(obj1, obj2); // or as T & U
}

const merged = merge({ name: "Max" }, 30); // {name: max},
// Object.assign fails silently if not in strict mode since the second parameter is not an object
merged.age.toString(); // error, can not access toString() of undefined

// constraint, solution
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
    return Object.assign(obj1, obj2); // or as T & U
}

const merged = merge({ name: "Max" }, 30); // error, 30 is not an object
  • add constraint so that the passed generic type is guaranteed to have a specific property.
type WithLength = { length: number };

function describeLen<T extends WithLength>(x: T): [T, string] {
    if (x.length > 1) {
        // without this constraint, it will be an error, since length is not on type T
        return [x, `x has ${x.length} elements`];
    }
    return [x, `x has no elements`];
}

describeLEn("abc"); // "x has 3 elements", string has a length property
describeLen(["a", "b", "c"]); // "x has 3 elements", array has a length property
describeLen({ length: 10 }); // "x has 10 elements", object has a length property
describeLen({}); // error, object has no length property
describeLen(1); // error, number has no length property
  • add constraint so that the passed generic type is guaranteed to have a specific property that belongs to a specific type.
function extractAndConvert<T extends object, U extends keyof T>(obj: T, key: U): T[U] {
    // using keyof, you can only access the properties of the object that are defined in the type T
    // we can also know the return type of the property obj[key]
    return obj[key];
}

Generic Classes

  • if the data in the class is generic, you can specify the type of the data in the class.
class GenericStorage<T extends number | string | boolean> {
    data: T[] = [];
    add: (x: T) => {
        this.data.push(x);
        return this.data;
    };
}

const storage = new GenericStorage<number>();
storage.add(1); //works
storage.add("a"); //error, "a" is not a number

const s2 = new GenericStorage<object>(); //error, object is not allowed (T extends number | string | boolean)

convert an array of strings to literal union type

const arr = ["a", "b", "c"]; as const;  // as const, makes array as ReadonlyArray<"a"|"b"|"c">
type ArrKeys = typeof arr[number]; // "a" | "b" | "c"

Pick and Omit

const obj = {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: 5,
    f: 6,
};

const keysToPick = ["a", "b", "c"] as const; // as const, makes array as ReadonlyArray<"a"|"b"|"c">
type PickedKeys = typeof keysToPick[number]; // convert string[] to literal string union type, so it can be used in Pick
const picked: Pick<typeof obj, PickedKeys> = { a: 1, b: 2, c: 3 };

const keysToOmit = ["a", "b", "c", "d", "e"] as const; // as const, makes array as ReadonlyArray<"a"|"b"|"c"|"d"|"e"">
type OmittedKeys = typeof keysToOmit[number]; // convert string[] to literal string union type, so it can be used in Omit
const omitted: Omit<typeof obj, OmittedKeys> = { f: 6 };

Extract and Exclude

  • Exclude expects 2 union types, and returns a union type of the excluded members.
const arr = ["a", "b", "c"] as const; // as const, makes array as ReadonlyArray<"a"|"b"|"c">
type ArrKeys = typeof arr[number]; // "a" | "b" | "c"
type ExecutedKeys = Exclude<ArrKeys, "b">; // "a" | "c"

type Todo = {
    title: string;
    description: string;
    completed: boolean;
};

const keys = ["title", "completed"] as const;
type keysT = typeof keys[number];
type Excluded = Exclude<keyof Todo, keysT>; // "description"
  • Extract\ returns the a type that’s the intersection of Type and Union
type Todo = {
    title: string;
    description: string;
    completed: boolean;
};

const keys = ["title", "something", "anotherKey"] as const;
type keysT = typeof keys[number];

type ExtractTodo = Extract<keyof Todo, keysT>; // "title"

NonNullable

  • NonNullable \ returns a new type excluding *null and undefined
type T = string | null | undefined;
type NonNullableT = NonNullable<T>; // string

Utility types of Functions

  • Parameters<T> returns the type of the parameters of the function T
declare function f1(arg: { a: number; b: string }): void;

type ParametersOfF1 = Parameters<typeof f1>; // type T3 = [arg: { a: number; b: string;}]
type T0 = ParametersOfF1[0]; // { a: number; b: string; }

type T1 = Parameters<string>; // error, string is not a function
  • ReturnType<T> returns the type of the return value of the function T
  • ReturnType \ expects a function type and returns the return type of the function.
declare function f1(arg: { a: number; b: string }): void;
type T0 = ReturnType<typeof f1>; //  void
type T8 = ReturnType<Function>; // error, Function is not a `function type` as (arg: any) => any

Utility types of classes

  • ConstructorParameters<T> returns the type of the parameters of the constructor of the constructor function T
  • ConstructorParameters \ expects a constructor function type and returns a tuple type of its parameters
type T0 = ConstructorParameters<ErrorConstructor>; // [message?: string]
  • InstanceType \ returns the type of the instance of the class T
class C {
    x = 0;
    y = 0;
}

type T0 = InstanceType<typeof C>; // C

Generic Type of any class

function ModifyClassDecorator (originalClass: <T extends { new (...args: any[]): Record<string, any> }> ){
    return new class extends originalClass {
    constructor(...args: any[]) {
        // ...
        super(...args);

    }
    }
}

@ModifyClassDecorator
class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const p = new Person("John", 30);