泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
不用泛型的示例,传入什么值返回什么值:
function identity(arg: number): number {return arg;}
也可使用 any,但是却会丢失类型信息
function identity(arg: any): any {return arg;}
使用泛型:
function identity<T>(arg: T): T {return arg;}
不同于使用 any,泛型不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。
定义了泛型函数后,可以用两种方法使用。
第一种是,传入所有的参数,包含类型参数:
let output = identity<string>("myString"); // type of output will be 'string'
这里我们明确的指定了T是string类型,并做为一个参数传给函数,使用了<>括起来而不是()。
第二种方法更普遍。利用了类型推论 — 即编译器会根据传入的参数自动地帮助我们确定T的类型:
let output = identity("myString"); // type of output will be 'string'
注意我们没必要使用尖括号(<>)来明确地传入类型;编译器可以查看myString的值,然后把T设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。
泛型方法
先看一个简单的示例:
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']
上例中,我们在函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了。
接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来:
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']
接收一个数组
可以指定传入的参数是一个 Array,这样就可以使用 Array 的一些属性和方法:
function loggingIdentity<T>(arg: T[]): T[] {console.log(arg.length);console.log(arg.join());return arg;}
也可写成:
function loggingIdentity<T>(arg: Array<T>): Array<T> {console.log(arg.length);console.log(arg.join());return arg;}
多个类型参数
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {return [tuple[1], tuple[0]];}swap([7, 'seven']); // ['seven', 7]
上例中,我们定义了一个 swap 函数,用来交换输入的元组。
泛型参数的默认类型
在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
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;}
泛型接口
有以下泛型定义的方法:
let myIdentity: <U>(arg: U) => U = function <D>(arg: D): D {return arg;};// orlet myIdentity: <U>(arg: U) => U = <D>(arg: D): D => {return arg;};
改为接口约束的形似:
interface GenericIdentityFn {<T>(arg: T): T;}let myIdentity: GenericIdentityFn = function <T>(arg: T): T {return arg;};// orlet myIdentity: GenericIdentityFn = <T>(arg: T): T => {return arg;};
其中T只是一个泛型的形式标识,也可写为任意其他标识(U、D等)
我们可以把泛型参数提前到接口名上。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary<string>而不只是Dictionary)。这样接口里的其它成员也能知道这个参数的类型了:
interface GenericIdentityFn<T> {(arg: T): T;}let myIdentity: GenericIdentityFn<any> = <T>(arg: T): T => {return arg;};
一个简单的示例:
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']
或者:
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']
泛型类
与泛型接口类似,泛型也可以用于类的类型定义中:
class Generic<T> {zeroValue: T;add: (x: T, y: T) => T;}let numberGeneric = new Generic<number>();numberGeneric.zeroValue = 0;numberGeneric.add = function (x, y) { return x + y; };console.log(numberGeneric.add(1, 2)) // 3let stringrGeneric = new Generic<string>();stringrGeneric.zeroValue = "";stringrGeneric.add = function(x, y) { return x + y; };console.log(stringrGeneric.add('Hello ', 'world')) // Hello world
泛型约束
之前的一个例子,我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 在loggingIdentity例子中,我们想访问arg的length属性,但是编译器并不能证明每种类型都有length属性,所以就报错了。
function loggingIdentity<T>(arg: T): T {console.log(arg.length); // Error: T doesn't have .lengthreturn arg;}
相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。
为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:
interface Lengthwise {length: number;}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length); // Now we know it has a .length property, so no more errorreturn arg;}
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property
我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity([])loggingIdentity('')loggingIdentity({length: 10, value: 3});
多个类型参数之间也可以互相约束:
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 });
上例中,我们使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。
