Generic basis

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.

Instead of generics, use any to define functions:

function identity(arg: any) :any {
    return arg;
}
Copy the code

Using the any type causes the function to accept arg arguments of any type, thus missing some information: the type passed in should be the same as the type returned. If a number is passed in, only any type of value can be returned.

Therefore, you need a way to make the type of the return value the same as the type of the parameters passed in. A type variable is used here, which is a special kind of variable that only represents a type and not a value.

function identity<T> (arg: T) :T {
    return arg;
}
Copy the code

Identity adds the type variable T. T helps capture the type (for example: number) passed in by the user and can be used. Then I used T again as the return value type. The parameter type is now the same as the return value type, allowing information about the type used in the function to be tracked.

Once you define a generic function, you can use it in two ways. The first is to pass in all parameters, including the type parameter:

let output = identity<string> ("myString");  // type of output will be 'string'
Copy the code

This specifies that T is a string and is passed to the function as an argument, using <> brackets instead of ().

The second method is more common. Type inference is used — that is, the compiler automatically helps us determine the type of T based on the parameters passed in:

let output = identity("myString");  // type of output will be 'string'
Copy the code

There is no need to use Angle brackets (<>) to explicitly pass the type; The compiler can look at the value of myString and set T to its type. Type inference helps keep code lean and readable.

The generic definition

The type of a generic function is no different from that of a non-generic function, except that it has a type argument first, just like a function declaration:

function identity<T> (arg: T) :T {
    return arg;
}
​
let myIdentity: <T>(arg: T) = > T = identity;
Copy the code

You can also use different generic parameter names, as long as they match in number and usage.

function identity<T> (arg: T) :T {
    return arg;
}
​
let myIdentity: <U>(arg: U) = > U = identity;
Copy the code

You can also define generic functions using object literals with call signatures:

function identity<T> (arg: T) :T {
    return arg;
}
​
let myIdentity: {<T>(arg: T): T} = identity;
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]];
}
​
// Defines a swap function that swaps input tuples.
swap([7.'seven']); // ['seven', 7]
Copy the code

Generic constraint

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

At this point, we can constrain the generics to allow the function to pass only those variables that contain the property. 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 can also set this to the default for arrays of type T.

function loggingIdentity<T> (arg: T[]) :T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}
​
function loggingIdentity<T> (arg: Array<T>) :Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}
Copy the code

Multiple type parameters can also be constrained against each other:

// In this example, we use two type arguments that require T to inherit from U, thus ensuring that U does not have a field that does not exist in T.
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

A generic interface

Take the object literal from the example above and use it as an interface:

interface GenericIdentityFn {
    <T>(arg: T): T;
}
​
function identity<T> (arg: T) :T {
    return arg;
}
​
let myIdentity: GenericIdentityFn = identity;
Copy the code

In the example above, we can treat a generic parameter as a parameter to the entire interface:

interface GenericIdentityFn<T> {
    (arg: T): T;
}
​
function identity<T> (arg: T) :T {
    return arg;
}
​
let myIdentity: GenericIdentityFn<number> = identity;
Copy the code

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

A generic class

Similar to 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, you 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

Use class types in generics

When TypeScript uses generics to create factory functions, you need to reference the class type of the constructor. For instance,

function create<T> (c: {new(): T; }) :T {
    return new c();
}
Copy the code

The following example uses the stereotype properties to infer and constrain the relationship between the constructor and the class instance:

class BeeKeeper {
    hasMask: boolean;
}
​
class ZooKeeper {
    nametag: string;
}
​
class Animal {
    numLegs: number;
}
​
class Bee extends Animal {
    keeper: BeeKeeper;
}
​
class Lion extends Animal {
    keeper: ZooKeeper;
}
​
function createInstance<A extends Animal> (c: new () => A) :A {
    return new c();
}
​
createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!
Copy the code