TypeScript相关知识点整理_泛型

244 阅读7分钟

泛型

泛型变量

为何使用泛型?

  1. 类型声明时 无法确定 具体类型,仍然需要 对输入输出的参数 能有效提示 的情况下,定义 泛型变量 来约束类型,如果使用any无法 确认函数输入输出之间的关系 以及对输出类型 校验
  2. 利用泛型对 更深层的子类型 进行明确,可以帮助开发过程获得更多提示

举个粗浅的🌰

//假设:str定义了string类型
let str = 'test';

//假设:并不确定传参,定义了any 并且 没有设置类型守卫
function doubleNum(_str:any):any{
    return _str
}

//假设:..中间有一大堆逻辑

//此时方法传入了str any类型TS不会进行校验,此处得不到有效提示,
//到底传入一个string类型,这个方法要给我返回一个什么类型的值
//这个方法现在可以接受什么值也得不到提示
//好的。既然没有提示,那就command+左键快捷点到方法那里看一下逻辑~~~~
//可行。但这样来回切换确认类型和逻辑的关系挺累的
doubleNum(str)

TS 并不能给出有效提示,只有个any,返回到底是啥咱不知道: 截屏2023-07-14 08.49.03.png
现在换一种方式:

function doubleNum<T>(_str:T):T{
    return _str
}

//现在泛型输入了string类型,TS识别并通过类型校验,知道返回值也会返回string
doubleNum<string>(str)

TS就能执行其类型校验的功能,并给出提示: 截屏2023-07-14 08.47.39.png

泛型变量语法

  • <T>表示,此处的 T 只是变量,可以任意命名,约定俗成:
    • T (type)表示类型
    • K (key)表示对象键的类型
    • V (value)表示对象值的类型
    • E (element)表示元素的类型
  • 泛型变量可以在类型分配或变量方法赋值时给定具体类型
    function identity<T>(arg: T): T {  //当作整个类型使用
        return arg;
    }
    
    //1.传入所有的参数,包含类型参数
    let output = identity<string>("myString");
    
    //2.利用TS类型推论,省略类型注解
    let output1 = identity("myString");
    

泛型约束

实现方式:

  • 使用 extends 关键字
    function FunName<T extends SomeType>(someProps: T)
    
    • 泛型变量在类型声明时没有确定类型,会导致在类型分配环节 泛型表示的 类型范围 过大,可能和 逻辑冲突。因此需要对类型进行一定的约束保证实际使用时的逻辑匹配类型
    • 如下:对object类型,使用 extends 进行泛型约束
      function merge<T extends object, U extends object>(objA: T, objB: U) {
          return Object.assign(objA, objB);
      }
      
      const mergeObj = merge({ user: "Nikcy" }, { age: 30 });
      //此时mergeObj会被 TS推断为两个对象的融合类型,因此使用mergeObj 会提示 user和age
      
    • 泛型约束能专注于所需要的具体结构,又使结构具备扩展灵活性
    • 如下:泛型约束后,只需结构中包含length属性即可
      interface Length {
          length: string
      }
      //继承接口的泛型
      function loggingIdentity1<T extends Length>(arg: T): T {
          console.log(arg.length);   
          return arg;
      }
      
      type Length0 = string;
      //继承已知类型
      function loggingIdentity2<T extends Length0>(arg: T): T {
          console.log(arg.length); //字符串类型具备length   
          return arg;
      }
      
  • 使用 keyof 操作符
    • 泛型变量 可存在多个,利用 keyof 还能定义由泛型对象键名组成的联合类型
    //1.
    function getVal<T>(obj: T, key: keyof T) {
      return obj[key];
    }
    //2.
    function getVal<T, U extends keyof T>(obj: T, key: U) {
      return obj[key];
    }
    
    console.log(getVal({ name: "Nicky", age: 30 }, "name")); //Nicky
    console.log(getVal(["a", "b", "c"], 1)); //b
    

交叉类型 & 多重泛型约束

  • 交叉类型:用& 操作符将一些已知类型融合成一个新类型,此类型包含所有已知类型的特性
     interface Colorful {
         color: string;
     }
     interface Circle {
         radius: number;
     }
     type ColorfulCircle = Colorful & Circle;
     let colorC: ColorfulCircle = {
         color: 'red',
         radius: 10
     }
    
  • 区别于联合类型。联合类型用|操作符连接一些已知类型,此类型只表示类型之一的特性
    • 如果将联合类型用作返回值的类型声明,TS无法确认具体返回是哪一种类型(TS不会根据逻辑修正原始类型的定义),因此返回值会要求是多个类型的交集特性
      interface Bird {
          fly();
          layEggs();
      }
      
      interface Fish {
          swim();
          layEggs();
      }
      
      function getSmallPet(): Fish | Bird {
          var back: Fish = {
              swim() {},
              layEggs() {}
         }
          return back
      }
      //即使此方法中逻辑返回就是Fish类型,TS也只会识别类型声明的联合类型,依旧会判定其具备另一种类型可能
      //因此要么同步修改类型声明,要么使用断言指定某一种具体类型
      let pet = getSmallPet();  
      pet.layEggs(); // okay
      pet.swim();    // errors
      
    • 多重泛型约束:泛型 + 继承 + 交叉类型
          interface Music {
              url: string
          }
          interface Movie {
              quoto: string;
              actors: string
          }
      
          class Artist <T extends Music & Movie> {  //泛型类
              constructor(public arg: T) {  //采用修饰符传参默认为 this.arg = arg
              }
          }
      
          let art = new Artist({
              url: 'outside',
              quoto: 'art is all',
              actors: 'Julie'
          });
          art.arg.url;  //类型为string
      

泛型类型

泛型接口的声明方式

  • 类型声明时使用泛型变量的接口
    //define a generic type
    interface GenericType<T> {
        (param:T):T
    }
    //to use the generic type, needing a specific type
    let useGeneric0: GenericType<string> = function(a) {
        return a
    }
    //like this
    let genericType: Array<string> = ['1'];  //利用泛型类型的Array声明了变量的具体类型并赋值
    
  • 接口中泛型变量的位置
    //1.这种接口类型声明的对象方法为泛型函数,但是其他属性有具体的类型注解
    interface GenericIdentityFn {
        <T>(arg: T): T;
        noGener: string
    }
    
    //2.泛型变量定义在接口上时,此接口为泛型接口,此泛型变量可以作用于内部所有方法或属性注解上
    interface GenericIdentitySpecify<T> {
        (arg: T): T;
    }
    

泛型函数的声明方式

  • 函数注解时用泛型变量的为泛型函数
     function literalFun<T>(params: T):T {
         return params
     }
     
     //对象字面量的泛型类型声明
     let literalType: {<T>(params: T): T} = literalFun;
     
     //函数表达式的泛型类型声明
     let funType: <T>(param: T) => T = literalFun
    

泛型函数 & 泛型接口 &泛型类

  • 三种对比
    //函数声明方式的泛型函数——类型注解+函数实现
    function literalFun<T>(params: T):T {
        return params
    }
    //函数表达式方式的泛型函数
    let literalFun:<T>(params: T)=> T = function(param){
        return params
    }
    //泛型接口
    interface GenericInter<T> {
        proper0: T;
        proper1: number;
        //... so on
        (arg: T)=> T;
    }
    //泛型类
    class GenericNumber<T> {
        zeroValue: T;
        add: (x: T, y: T) => T;
    }
    

泛型类型 & 联合类型 区别

  • 联合类型和泛型类型的具体类型只有在 运行时 才能确认
  • 联合类型是多种类型的集合,逻辑处理搭配类型守卫才能准确区分各种类型的处理方式;如:string类型的length属性,number类型不具备
  • 泛型类型用于指代 具备某种特性的单一类型,只强调类型中的必要结构,强调类型;如:需要使用带有length的泛型,不在乎是string还是Array类型

扩展

  • 泛型类型,any类型,联合类型声明的函数区别:
    //联合类型函数:写法复杂
    function noGeneric0(x: number | string):number | string {
        return x
    }
    //泛型函数
    function generic0<T>(param:T): T {
        return param
    }
    //任意类型函数:传参和返回值之间无法确定明确的类型关联
    function generic1(x:any): any {
        return x + ''
    }
    
    //联合和泛型写法在编写及编译过程都有类型检查,返回理想类型,可以帮助规避运行时异常
    console.log(typeof noGeneric0(1), typeof noGeneric0('1'));  //number string
    console.log(typeof generic0(1), typeof generic0('1'));  //number string
    //输入输出的参数类型之间没有关联,由函数逻辑决定,错误检查无效 
    console.log(typeof generic1(1), typeof generic1('1'));  //string string
    
  • keyof的使用
    • 作为TS2.1引入的操作符,可以用于获取某个类型中定义的所有键名,其返回类型是所有键名组成的联合类型
    interface Person {
        name: string;
        age: number
    }
    
    type K0 = keyof Person;  //num | age 字符串键名的联合类型
    // let k0: K0 = 'other'; //error 不能将other分给 ‘name’ | 'age'
    let k0: K0 = 'name';
    
    type K1 = keyof Array<Person> ;   //泛型写法等价于Person[]
    let k1: K1;  //// number | "length" | "push" | "concat" | ...   
    
    type K2 = keyof { [x: string]: Person };  // string | number
    
  • 定义了泛型变量的类型,当用作类型声明的时候需要赋具体的类型,这种具体类型叫做类型参数
    let output = identity<string>("myString");  // 此处传入的string叫做类型参数