Type Script --泛型 学习
一. 场景
如何支持多种类型,并且还能保证数据完整性?
思考:
普通函数的样子?
定义参数->返回值。
二. 实现
- 泛型:
在支持多种类型参数的同时,数据的完整性还能得到保证。 - 定义:类型变量
指特殊的一种变量,只表示类型,不提供值。类型变量可以自定义,只要在数量上和使用方式上能对应上就可以。 - 最基本的实现方法:
通过类型变量替换掉参数的类型,<T>是函数签名,用来表示其是一个泛型函数。
function indentity <T>(arg: T){
return arg;
}
我们有两种方法来使用此函数:
①声明返回值
let output = indentity<string> ("its me!");
② 无返回声明(类型推论)
let output = indentity ("its me!");
利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型。
- 泛型变量
当参数类型被标记为泛型变量后,那么当在方法体内使用这个参数时,必须把他当成任意类型。你不能在方法体内使用某些特定类型的方法,如使用arg.length(),因为你传递过来的可能是任何值,而数字就没有length方法。 - 任意类型的数组
我们平时使用数组时,通常会声明此数组的类型,如:args : string[] 。此数组只能传递字符串类型。通过学习泛型,如果我们把这个数组改为泛型数组,那么我们就可以传递任意类型的值了。
现在我们声明为 args : T[] 。
function loggingIdentity <T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
我们既可以使用数组的方法,又可以传递任意的值了。
- 泛型类型
思考:我们可以声明泛型数组以外的泛型类型吗?
首先,我们看一下使用带有调用签名的对象字面量来定义泛型函数的写法:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
函数的泛型类型表示为约束函数的 参数类型 与 返回值类型。<T>表示函数签名。
这种写法等同于:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T)=>T = identity;
对象字面量 可以单独拿出来,用来定义一个接口:
interface GenericIdentityFn {
<T>(arg: T): T;
}
然后把这个接口作为函数的类型:
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity : GenericIdentityFn = identity;
如果我们把函数签名从接口声明中拿出来,放到接口后面,就像这样:
interface GenericIdentityFn<T>{
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity : GenericIdentityFn<number> = identity;
那么,当我们使用接口声明的时候,就需要同时传入<T>的类型,如上面例子里的number类型。可以根据需要,约束使用时的类型了。
接下来,在接口的基础上,学习如何创建泛型类。
- 泛型类
泛型类跟泛型接口差不多,同样是用<T>放在类名称后面,如
class GenericNumber<T>{
zeroValue:T ;
add:(x:T,y:T)=>T;
}
add属性被声明为一个函数名称,并约束了参数类型与返回值类型,但是此时并没有方法体。
当初始化这个类的时候,可以用任意类型,比如使用number类型:
let myGenericNumber=new GenericNumber<number>();
myGenericNumber.zeroValue:T =0;
myGenericNumber.add = (x,y)=>x+y;
alert(myGenericNumber.add(myGenericNumber.zeroValue, 2)); //2
同样,也可以初始化的时候,传入string:
let myGenericNumber=new GenericNumber<number>();
myGenericNumber.zeroValue:T ="1";
myGenericNumber.add = (x,y)=>x+y;
alert(myGenericNumber.add(myGenericNumber.zeroValue, "hello")); //1hello
需要注意的是,泛型类里不能使用静态属性。原因是静态属性是类在声明时就进行初始化,跟类的实例无关。
- 泛型约束
我们还可以对<T>的内容进行约束,比如T类型里面应该必须包含某一个属性,以方便我们程序的使用。
还以接口为例子,这时候,我们需要在接口里面声明这个属性。
interface Lengthwise {
length: number;
}
当使用泛型继承这个接口,那么就要求 实现T类型的类型里,必须包含length这个属性。
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3}); //10 //right,{length: 10, value: 3}对象 包含 length属性。
另外的约束使用:K类型被T类型约束。本例里K类型必须被包含在T里。
function getProperty(obj: T, key: K) {
return obj[key];
}
让我们最后看一下这个例子,你会怎么理解它呢?
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
class Dog{
numLegs: number;
}
class Bird{
hasMask: boolean;
nametag: string;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
createInstance(Bee).numLegs; // typechecks!
createInstance(Dog).numLegs; // typechecks!
createInstance(Bird).hasMask; //error typechecks
createInstance(Bird).nametag; //error typechecks
解释:
因为泛型A继承了Animal的约束,所以调用方法createInstance()时,参数对象必须包含Animal 类的属性 :numLegs;
参数(c: new () => A)会返回一个名为A的对象,但是没有值。方法体通过调用 new c() 这个构造方法生成一个类型为A的对象。
正如:createInstance(Bird).hasMask; //error typechecks
createInstance(Bird).nametag; //error typechecks
会报错:
Argument of type 'typeof Bird' is not assignable to parameter of type 'new () => Animal'.
Type 'Bird' is not assignable to type 'Animal'.
Property 'numLegs' is missing in type 'Bird'.
而Dog因为包含了Animal的numLegs 属性,则类型检测通过。