Generic programming

Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters.
把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数T
function identity( arg: any ): any { return arg; }
We need a way of capturing the type of the argument in such a way that we can also use it to denote what is being returned. Here, we will use a type variable, a special kind of variable that works on types rather than values.
function identity<T>( arg: T ): T { return arg; }
function identity( something: number | string ): number | string { return something; } // Type 'string | number' is not assignable to type 'number'. // Type 'string' is not assignable to type 'number'.(2322) const a: number = identity( 123 ); const b: number = identity( 123 ) as number;
function identity<T>( something: T ): T { return something; } const a: number = identity( 123 ); // Type 'string' is not assignable to type 'number'.(2322) const b: number = identity( 'string' ); const c: string = identity( 'sting' ); // Type 'number' is not assignable to type 'string'.(2322) const d: string = identity( 123 );
function identity<T extends string | number>( something: T ): T { return something; } const a: string = identity( 'string' ); const b: number = identity( 123 ); // Type 'string | number' is not assignable to type 'boolean'. // Type 'string' is not assignable to type 'boolean'.(2322) // Argument of type 'boolean' is not assignable to parameter of type 'string | number'.(2345) const c: boolean = identity( true );

Generic Types

The type of generic functions is just like those of non-generic functions, with the type parameters listed first, similarly to function declarations:
function identity<T>( arg: T ): T { return arg; } const myIdentity: <T>( arg: T ) => T = identity; // using a different name for generic type parameter in the type. const anotherIdentity: <U>( arg: U ) => U = identity; // writing the generic type as a call signature of an object literal type const the3rdIdentity: { <T>( arg: T ): T } = identity;
In a similar example, we may want to move the generic parameter to be a parameter of the whole interface. This lets us see what type(s) we're generic over. This makes the type parameter visible to call the other members of the interface.
interface GenericIdentityFn<T> { ( arg: T ): T; } function identity<T>( arg: T ): T { return arg; } const myIdentity: GenericIdentityFn<number> = identity;
Notice that out example has changed to be something slightly different. Instead of describing a generic function, we now have a non-generic function signature that is a part of the generic type.
// Try it out on TypeScript Playground interface QueueManager<T> { list: T[]; push: ( ...args: T[] ) => number; shift: () => T | undefined; get: ( index: number ) => T | undefined; } class Person { constructor( public name: string ) {} } class PersonQueueManager implements QueueManager<Person> { constructor( public list: Person[] ) {} push( ...args: Person[] ): number { return this.list.push( ...args ); } shift(): Person | undefined { return this.list.shift(); } get( index: number ): Person | undefined { return this.list[ index ]; } }
// Property 'push' in type 'PersonQueueManager' is not assignable to the same property in base type 'QueueManager<Person>'. // Type '(...args: string[]) => Person' is not assignable to type '(...args: Person[]) => Person'. // Types of parameters 'args' and 'args' are incompatible. // Type 'Person' is not assignable to type 'string'.(2416) push( ...args: string[] ): Person { return this.list.push( ...args ); }
⚠️
In addition to generic interfaces, we can also create generic classes. Note that it is not possible to create generic enums and namespace.

Generic Classes

 
// Try it out on TypeScript Playground interface QueueManager<T> { list: T[]; push: ( ...args: T[] ) => number; shift: () => T | undefined; get: ( index: number ) => T | undefined; } class Person { constructor( public name: string ) {} } class PersonQueueManager<T> implements QueueManager<T> { constructor( public list: T[] ) {} push( ...args: T[] ): number { return this.list.push( ...args ); } shift(): T | undefined { return this.list.shift(); } get( index: number ): T | undefined { return this.list[ index ]; } } const a = new PersonQueueManager( [ 1, 2, 3 ] ); a.push( 1 ); // Argument of type 'string' is not assignable to parameter of type 'number'.(2345) a.push( 'string' ); const b = new PersonQueueManager( [] ); b.push( 1 ); const c = new PersonQueueManager<number>( [] ); // Argument of type 'number' is not assignable to parameter of type 'never'.(2345) c.push( 1 ); // Type 'number' is not assignable to type 'Person'.(2322) new PersonQueueManager<Person>( [ 1, 2, 3, ] ); new PersonQueueManager<Person>( [ new Person( 'Achilles' ) ] );
As we covered in our section on classes, a class has two sides to its type: the static side and the instance side. Generic classes are only generic over their instance side rather than their static side, so when working with classes, static members can not use the class's type parameter.
// Try is out on TypeScript Playground interface QueueManager<T> { list: T[]; } class Person { constructor( public name: string ) {} } class PersonQueueManager<T> implements QueueManager<T> { constructor( public list: T[] ) {} // Static members cannot reference class type parameters.(2302) // Parameter 'list' of public static method from exported class has or is using private name 'T'.(4070) static len( list: T[] ): number { return list.length; } }

Generic Constraints

Use the extends keyword to denote our constraint:
// Try it out on TypeScript Playground interface QueueManager<T> { list: T[]; } class Person { constructor( public name: string ) {} } class PersonQueueManager<T> implements QueueManager<T> { constructor( public list: T[] ) {} } new PersonQueueManager<number>( [ 1, 2, 3 ] ); class StrictPersonQueueManager<T extends Person> implements QueueManager<T> { constructor( public list: T[] ) {} } // Type 'number' is not assignable to type 'Person'.(2322) new StrictPersonQueueManager( [ 1, 2, 3 ] ); // Type 'number' does not satisfy the constraint 'Person'.(2344) new StrictPersonQueueManager<number>( [] );

Using Type Parameters in Generic Constraints

 
You can declare a type parameter that is constrained by another type parameter.
function getProperty<T, K extends keyof T>( obj: T, key: K ) { return obj[ key ]; }
The order of the parameters can also be changed.
// Try it out on TypeScript Playground function getProperty<T extends keyof K, K>( key: T, obj: K ) { return obj[ key ]; } getProperty( 'name', { name : 'Achilles' } ); // Argument of type '"age"' is not assignable to parameter of type '"name"'.(2345) getProperty( 'age', { name : 'Achilles' } );

Using Class Types in Generics

When creating factories in TypeScript using generics, it is necessary to refer to class types by their constructor functions. For example,
function create<T>( c: { new (): T } ): T { return new C(); }
// Try it out on TypeScript Playground class Person { constructor( public name: string ) {} } function createInstance<T>( name: string, obj: Record<string, { new (): T }> ): T | void { if( !Reflect.has( obj, name ) ) return; return new obj[ name ](); } function createAnotherInstance<T>( name: string, obj: Record<string, { new ( ...args: any[] ): T }> ): T | void { return new obj[ name ](); } // Type 'typeof Person' is not assignable to type 'new () => Person'.(2322) createInstance<Person>( 'name', { name : Person } ); createAnotherInstance<Person>( 'name', { name : Person } );

Mapped types

A common task is to take an existing type and make each of its properties optional:
interface Person { name: string; age: string; }
badge