美文网首页程序员
Type Script --泛型 学习

Type Script --泛型 学习

作者: 柳源居士 | 来源:发表于2018-09-30 22:07 被阅读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 属性,则类型检测通过。

    相关文章

      网友评论

        本文标题:Type Script --泛型 学习

        本文链接:https://www.haomeiwen.com/subject/jfsjoftx.html