The generic

Generics is a feature that defines a function, interface, or class without specifying a specific type in advance, but instead specifies the type when it is used.

Let’s implement a function createArray that creates an array of specified lengths and populates each item with a default value:

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

createArray(3, 'x'); // ['x', 'x', 'x']
Copy the code

We use the array generics mentioned earlier to define the type of the return value.

This code compiles without error, but one obvious drawback is that it does not define the exact type of return value:

Array allows each item in an Array to be of any type. But what we expect is that every item in the array should be the type of the value that was entered.

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

createArray<string>(3, 'x'); // ['x', 'x', 'x']
Copy the code

We added T to the function name, where T is used to refer to any input type, followed by value: T and the output Array.

Then, when called, you can specify its specific type as String. Of course, it is also possible not to specify manually, and let the type inference deduce it automatically:

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

createArray(3, 'x'); // ['x', 'x', 'x']
Copy the code

Multiple type parameters

When defining generics, you can define more than one type parameter at a time:

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

swap([7, 'seven']); // ['seven', 7]
Copy the code

In the example above, we defined a swap function to exchange input tuples.

Generic constraint

When we use a generic variable inside a function, we don’t know what type it is, so we can’t arbitrarily manipulate its properties or methods:

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

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

In the example above, the generic T does not necessarily contain the length attribute, so the compile error was reported.

At this point, we can constrain the generics to allow the function to pass only those variables that contain the length attribute. This is the generic constraint:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
Copy the code

In the example above, we use the extends constraint that the generic T must conform to the Lengthwise shape of the interface, which means it must contain the length property.

If loggingIdentity does not contain length, an error will be reported at compile time:

interface Lengthwise {
    length: number;
}

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

loggingIdentity(7);

// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
Copy the code

Multiple type parameters can also be constrained against 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 });
Copy the code

In the example above, we used two type parameters that require T to inherit from U, thus ensuring that no field will appear on U that does not exist in T.

A generic interface

As you’ve seen before, you can use interfaces 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; }Copy the code

It is also possible to define the shape of a function using a generic interface:

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

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

createArray(3, 'x'); // ['x', 'x', 'x']
Copy the code

Further, we can advance the generic parameter to the interface name:

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

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

createArray(3, 'x'); // ['x', 'x', 'x']
Copy the code

Note that when using a generic interface, you need to define the type of the generic.

A generic class

Like generic interfaces, generics can also be used in the type definition of a 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; };
Copy the code

The default type of a generic parameter

Since TypeScript 2.3, we can specify default types for type parameters in generics. This default type is used when the type parameter is not specified directly in the code and cannot be inferred from the actual value parameter.

function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
Copy the code

reference

Introduction to TypeScript – ts.xcatliu.com/

TypeScript website – www.typescriptlang.org/