The generic type

In effect, you can extract a generic type as a generic type. In general, the use of type for generic functions may be as follows :(the type variable does not have to be T, but can be named by another name)

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

let myIdentity: <T>(arg: T) = > T = identity;
Copy the code

This would look a bit like declaring a function in JS using arrow functions.

In TypeScript, you can extract the arrow function part as an interface, that is, a generic interface:

interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity;
Copy the code

If a generic parameter is taken as an argument to the entire interface, as in the GenericIdentityFn argument T above, it is clear which generic type is being used, for example, passing in a number:

interface GenericIdentityFn<T> {
    (arg: T): T
}

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

let myIdentity: GenericIdentityFn<number> = identity;
Copy the code

In the example above, the interface is passed the parameter number, which means that when used, the generic type is number.

A generic class

In addition to generic interfaces, you can create generic classes, much like generic interfaces, that wrap generic types with <> after the class name.

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

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

In () is the parameter list of the generic type. For generic types, it is not necessarily number. This is also a parameter. Like interfaces, putting generic types in the class itself ensures that all attributes of the class use the desired type, ensuring type uniformity.

Generic constraint

In cases where the compiler cannot guarantee the presence of an attribute for every type, such as the number type that does not have the length attribute but needs to handle it, it may be better to limit the handling of the function rather than any. Then you need to have this property in the type you pass in, so this creates a notion of a constraint on the generic variable T.

Define an interface to describe constraints:

interface Lengthwise {
    length: number;
}

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

When a generic function is constrained, it is no longer applicable to any type. In use, a value that matches the constraint type must be passed:

loggingIdentity(3);  // Error, number doesn't have a .length property

loggingIdentity({length: 10.value: 3})
Copy the code

Use type parameters in generic constraints

You can declare a type parameter that is bound by another type parameter. For example, if you want to get a property from an object, but you want to make sure that the property you get exists, you can use another type parameter to constrain the behavior.

function getProperty(obj: T, key: K) {
    return obj[key];
}

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

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
Copy the code

Use class types in generics

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