程序员

Type Script --泛型 学习

2018-09-30  本文已影响35人  柳源居士

一. 场景
如何支持多种类型,并且还能保证数据完整性?
思考:
普通函数的样子?
定义参数->返回值。
二. 实现

  1. 泛型:
    在支持多种类型参数的同时,数据的完整性还能得到保证。
  2. 定义:类型变量
    指特殊的一种变量,只表示类型,不提供值。类型变量可以自定义,只要在数量上和使用方式上能对应上就可以。
  3. 最基本的实现方法:
    通过类型变量替换掉参数的类型,<T>是函数签名,用来表示其是一个泛型函数。
function indentity <T>(arg: T){
    return arg;
}

我们有两种方法来使用此函数:
①声明返回值

let output = indentity<string> ("its me!");

② 无返回声明(类型推论)

let output = indentity ("its me!");

利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型。

  1. 泛型变量
    当参数类型被标记为泛型变量后,那么当在方法体内使用这个参数时,必须把他当成任意类型。你不能在方法体内使用某些特定类型的方法,如使用arg.length(),因为你传递过来的可能是任何值,而数字就没有length方法。
  2. 任意类型的数组
    我们平时使用数组时,通常会声明此数组的类型,如:args : string[] 。此数组只能传递字符串类型。通过学习泛型,如果我们把这个数组改为泛型数组,那么我们就可以传递任意类型的值了。
    现在我们声明为 args : T[] 。
function loggingIdentity <T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

我们既可以使用数组的方法,又可以传递任意的值了。

  1. 泛型类型
    思考:我们可以声明泛型数组以外的泛型类型吗?
    首先,我们看一下使用带有调用签名的对象字面量来定义泛型函数的写法:
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类型。可以根据需要,约束使用时的类型了。
接下来,在接口的基础上,学习如何创建泛型类。

  1. 泛型类
    泛型类跟泛型接口差不多,同样是用<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

需要注意的是,泛型类里不能使用静态属性。原因是静态属性是类在声明时就进行初始化,跟类的实例无关。

  1. 泛型约束
    我们还可以对<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 属性,则类型检测通过。

上一篇下一篇

猜你喜欢

热点阅读