Generics refer to a feature that defines functions, interfaces, or classes without specifying specific types in advance, and then specifies types when used.
Generics are used to create reusable components that support multiple types of data.
Simple use
Defining functions using any type is similar to generics, but not really generics:
// Input variables of any type, but also return values of any type, inconsistent between the type of input and the type of return // Even if a number variable is passed in, it is still possible to return a string value function identity(arg: any): any { return arg; }
When an incoming type is consistent with the returned type, a type variable, which is a special variable, can be used to represent the type rather than the value:
function identity<T>(arg: T): T { return arg; } // Explicit incoming type parameters, specifying generic T as string, are generally used in correct type inference console.log(identity<string>('myString')); // It is more common to use type inference of TypeScript without passing in types. console.log(identity('myString'));
This is the generic type in TypeScript, which uses T to represent a certain type, so that the incoming type is consistent with the returned type.
Multiple Type Parameters
When defining generics, you can specify multiple type parameters:
function swap<T, U>(tuple: [T, U]): [U, T] { return [tuple[1], tuple[0]]; } // ['seven', 7] swap([7, 'seven']);
Generic constraints
When using a generic variable inside a function, because you don't know what type the variable is, TypeScript is not allowed to manipulate its properties or methods at will:
function loggingIdentity<T>(arg: T): T { // There is no attribute length on type T console.log(arg.length); return arg; }
However, a constraint can be imposed on generic variables so that TypeScript can more accurately infer the type of generic variables and provide some operational methods:
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { // T inherits the Lengthwise interface to access the length attribute console.log(arg.length); return arg; }
Multiple types of parameters can also be constrained by each other:
// T inherits U, T is the superset of U, so there will be no sub-end where T does not exist in U. 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 });
generic interface
Interfaces can define the shape a function needs to conform to, or they can use interfaces with generics to define the shape of a function:
interface CreateArrayFunc { <T>(length: number, value: T): Array<T>; // The generic parameter is considered as a parameter of the whole interface: (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']
generic class
Similar to generic interfaces, generics can also be used in class type definitions:
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; };
Generics specify default types
After TypeScript 2.3, you can specify default types for generic type parameters. When the actual type of the type parameter is not specified or the actual type cannot be inferred, TypeScript uses the default type as the type of the incoming parameter:
// The default parameter type for generics is string 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; }