泛型 在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
1 2 3 4 5 6 function identity<T>(arg : T): T { return arg; }identity (123 )identity ('123' )
代码中 T 代表 「Type」 ,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。
由于我们操作的是数组,所以.length属性是应该存在的。 我们可以像创建其它数组一样创建这个数组:
1 2 3 4 function loggingIdentity<T>(arg : T[]): T[] { console .log (arg.length ); return arg; }
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U
,用于扩展我们定义的 identity
函数:
1 2 3 4 5 6 function identity<T, U>(value : T, message : U): T { console .log (message); return value; }console .log (identity (666 , "jack is cool!" ));
泛型类型 && 泛型接口 泛型类型不同的注解方式:
函数泛型的注解方式
对象字面量的方式来定义泛型类型
泛型接口的定义方式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function identity<T>(arg : T): T { return arg; }let a : <T>(arg: T ) => T = identitylet b : { <T>(arg : T): T } = identityinterface IdentityInterface { <T>(arg : T): T }let c : IdentityInterface = identity
泛型类 & 泛型约束 泛型类 泛型类看上去与泛型接口差不多,我们只需要在类名后面,使用 <T, …> 的语法定义任意多个类型变量,具体示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class MinClass <T>{ public list : T[] = [] add (num: T ) { this .list .push (num) } min (): T { let minNum = this .list [0 ] for (let i = 0 ; i < this .list .length ; i++) { if (minNum > this .list [i]) { minNum = this .list [i] } } return minNum } }
我们在什么时候需要使用泛型呢?通常在决定是否使用泛型时,我们有以下两个参考标准:
当你的函数、接口或类将处理多种数据类型时;
当函数、接口或类在多个地方使用该数据类型时。
泛型约束 有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作用。
以官方文档例子为例:
我们需要去定义一个接口来描述约束条件。
创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:
1 2 3 4 5 6 7 8 interface LengthInterface { length : number }function loggingIdentity<T extends LengthInterface >(arg : T): T { console .log (arg.length ); return arg; }
其中,泛型约束用的是: extends 继承接口的方式(不一定非要是接口) 、T extends LengthInterface
用于告诉编译器,我们支持已经实现 Length 接口的任何类型
另外, 泛型约束并不一定用接口方式, 比如 我们可以把以上代码 接口 换成 类型别名 , 如下例子: // 以类型别名的方式依然可以
1 2 3 4 5 6 type LengthType = string function loggingIdentity<T extends LengthType >(arg : T): T { console .log (arg.length ); return arg; }
keyof 操作符 keyof操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
1 2 3 4 5 6 7 8 9 10 11 interface Person { name : string ; age : number ; location : string ; }type K1 = keyof Person ; type K2 = keyof Person []; type K3 = keyof { [x : string ]: Person };
可以看出, k1,k2,k3其实是键名
在泛型约束中使用类型参数 当我们理解了 keyof 操作符时, 看下面代码就很好理解了
1 2 3 4 5 6 7 8 function getProperty<T, K extends keyof T>(obj : T, key : K) { return obj[key]; }let x = { a : 1 , b : 2 , c : 3 , d : 4 };getProperty (x, "a" ); getProperty (x, "m" );
**而 K 就是指 ‘a’ , ‘b’ , ‘c’ , ‘d’ **
很明显通过使用泛型约束,在编译阶段我们就可以提前发现错误,大大提高了程序的健壮性和稳定性。
多重泛型约束 & 交叉类型 交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function extend<T, U>(first : T, second : U): T & U { let result = <T & U>{}; // 断言, 表示 result 包含 T U 俩种类型 for (let id in first) { (<any>result)[id] = (<any>first)[id]; // 将 first 中所有属性 给 result } for (let id in second) { if (!result.hasOwnProperty(id)) { (<any>result)[id] = (<any>second)[id]; // 将 second 中所有属性 给 result, 前提是result 没有该属性时 } } return result; // 返回的 result 就有 first 和 second 的所有属性 } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();
再举个简单例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 interface Sentence { content : string title : string }interface Music { url : string }class Test <T extends Sentence & Music >{ props : T constructor (public arg: T ) { this .props = arg } info ( ) { return { content : this .props .content , title : this .props .title , url : this .props .url } } }
泛型中的类类型 泛型中的类类型 目的是 约束或者更好的推论
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 ; createInstance (Bee ).keeper .hasMask ;