Generics

wang xiao bo 's photo
wang xiao bo
·Aug 31, 2021·

4 min read

Subscribe to my newsletter and never miss my upcoming articles

Generics refers to a feature that does not specify a specific type in advance when defining a function, interface, or class, but then specifies the type when using it.

First, let’s implement a function createArr, which can create an array of a specified length and fill each item with a default value.

function createArr(length: number, value: any): Array<any> {
    let result = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArr(4, 'y'); // ['y', 'y', 'y', 'y']

In the above example, we used the array generics mentioned before to define the type of the return value. This code compiles without error, but an obvious flaw is that it does not accurately define the type of the return value: Array allows each item of the array to be of any type. But what we expect is that each item in the array should be the type of the input value.

At this time, generics come in handy:

function createArr<T>(length:number, value: T): Array<T> {
    let res: T[] = []
      for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArr<string>(4, 'y')  // ['y', 'y', 'y', 'y']

In the above example, we added after the function name, where T is used to refer to the type of any input, which can be used in the following input value: T and output Array. Then when calling, you can specify its specific type as string. Of course, you can also not specify manually, and let the type inference be automatically calculated

Multiple type parameters When defining generics, you can define multiple type parameters at once:

function hand<T, U>(some: [T, U]): [U, T] {
    return [some[1],some[0]];
}

hand([8, 'eight']); // ['eight', 8]

Generic constraints When using a generic variable inside a function, because you don't know what type it is in advance, you can't manipulate its properties or methods at will:

function logging<T>(arg: T): T {
    console.log(arg.length);
    return arg;
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.

In the above example, the generic type T does not necessarily contain the attribute length, so an error was reported during compilation. At this time, we can constrain the generics and only allow this function to pass in variables that contain the length attribute. This is the generic constraint:

interface LengthHand {
    length: number;
}

function logging<T extends LengthHand>(arg: T): T {
    console.log(arg.length);
    return arg;
}
logging(8);

// index.ts(10,17): error TS2345: Argument of type '8' is not assignable to parameter of type 'LengthHand'.

We use extends to constrain that the generic T must conform to the shape of the interface Lengthwise, that is, it must contain the length attribute. At this time, if the incoming arg does not contain length when calling loggingIdentity, an error will be reported during the compilation stage

Multiple type parameters can also restrict each other:

function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = (<T>source)[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };

copyFields(x, { b: 10, d: 20 });

In the above example, we used two type parameters, in which T is required to inherit U, which ensures that fields that do not exist in T will not appear on U.

Generic interface You can use the interface to define the shape that a function needs to conform to:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

Of course, you can also use a generic interface to define the shape of the function:

interface CreateArrFunc<T> {
    (length: number, value: T): Array<T>;
}

let createArr: CreateArrFunc<any>;
createArr = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArr(4, 'y'); // ['y', 'y', 'y', 'y']

Generic class

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

Default type of generic parameter

After TypeScript 2.3, we can specify default types for type parameters in generics. When using generics without specifying the type parameter directly in the code, and it is impossible to infer from the actual value parameter, this default type will work.

function createArr<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
 
Share this