TS基础——泛型(1)

109 阅读3分钟

TS泛型.png

TS泛型

泛型的作用

在函数中使用泛型

  • 一个例子是,当一个函数的参数(当这个参数是对象时)可能具有多个类型时,首先我们可能想到联合类型来处理,但是当这些类型可能具有不同的属性的时候,联合类型就不能处理这种情况了,就需要使用泛型。
interface TypeA{
    a: 1,
    b: 2,
}

interface TypeB{
    b: 1,
    c: 1
}

function foo(a: TypeA | TypeB){
    console.log(a.b) //变量a使用了联合类型TypeA | TypeB,因此a在取属性的时候,只能使用TypeA和TypeB都有的属性。
    console.log(a.a) //类型检查出错,TypeB没有属性a
    console.log(a.c) //类型检查出错,TypeA没有属性c
}

泛型可以在定义函数、接口或类时,不预先指定具体的类型,但在使用的时候指定类型的特性。

函数中使用泛型

在函数声明时使用泛型

  • 在参数声明的括号前添加

  • 函数声明使用泛型一般结合接口或类型别名来使用。

    • 接口

      • 接口可以用于指定函数重载。
      interface fnType{
          <T>(len: number,iniVal: T): Array<T>;
          <T>(len: string,iniVal: T): Array<T>;
      }
      
      let fn: fnType;
      fn = function<T>(len: number | string,iniVal: T): Array<T>
      {
          const arr = [] as Array<T>;
          if(typeof len === 'number')
          {
              for(let i = 0;i < len;i++)
              {
                  arr[i] = iniVal;
              }
              return arr;
          }
          else{
              return arr;
          }
      }
      
      fn(1,1)
      fn('1','2')
      
      • 接口可以将写在接口名之后。

      interface fnType<T>{
          (len: number, iniVal: T): Array<T>;
      }
      
      let fn: fnType<any>;
      
      fn = function<T>(len: number,iniVal: T): Array<T>{
          const arr = [] as T[];
          for(let i = 0;i < len;i++)
          {
              arr[i] = iniVal;
          }
          return arr;
      }
      
      fn(1,1)
      fn(1,true)
      
    • 类型别名或其他没有在接口中做函数声明的情况

      type fnType1 = <T>(len: number, iniVal: T) => Array<T>;
      
      type fnType<T> = (len: number, iniVal: T) => Array<T>;
      
      let fn: fnType<any>;
      fn = function<T>(len: number, iniVal: T): Array<T>{
          const arr = [] as T[];
          for(let i = 0;i < len;i++)
          {
              arr[i] = iniVal;
          }
          return arr;
      }
      
      fn(2,'2')
      

在给出函数体定义时使用泛型

  • 在函数名后面添加,这个T将代指输入的参数的类型。
function createArr<T>(len: number,iniVal: T): Array<T>
{
  const arr = []
  for(let i = 0;i < len;i++)
  {
    arr[i] = iniVal;
  }
  return arr;
}
  • extends

    • extends的作用

      • 当使用泛型时,如果没有为泛型T限制类型,那么不能对其取属性,extends用于为T限制类型。

        • 使用了extends也相当于为泛型添加了约束,这时泛型只能接受包含某一属性的数据了。
        interface objType<U>{
            len: number;
            value: U;
          }
        
          function createArr<U,T extends objType<U>>(obj: T): Array<U>{
            const len = obj.len;
            const val = obj.value;
            const arr = [] as Array<U>;
            for(let i = 0;i < len;i++)
            {
                arr[i] = val;
            }
            return arr;
          }
        
          const obj: objType<string>  = {
            len: 2,
            value: 'val',
          }
        
          const obj1: objType<number> = {
            len: 2,
            value: 0,
          }
        
          console.log(createArr(obj))
          console.log(createArr(obj1))
        

        当T extends objType之后,泛型T只能接受包含objType中所有属性的接口、类型注解或类型别名(可以不限于objType,但必须包含,也就是objType是T的子集)。

  • 定义多个泛型

    • TS支持在使用泛型时定义多个泛型。

      function copy<T extends U, U>(source: U, target: T): T{
          for(let attr in source)
          {
              target[attr] = (<T>source)[attr]
          }
          return target
      }
      

在调用泛型函数的时候指定泛型的值

  • 调用含一个泛型的函数
function fn<T>(len: number,ini: T): Array<T>{
    const res = [] as T[];
    for(let i = 0;i < len;i++)
    {
        res[i] = ini;
    }
    return res;
}

fn<string>(2,'1') //相当于指定了泛型的类型,如果第二个参数不为string,那么会报错。
fn<number>(2,7)
  • 调用含多个泛型的函数
function fn<T extends U,U>(target: T, source: U): T{
    for(const attr in source)
    {
        target[attr] = (<T>source)[attr]
    }
    return target
}

console.log(fn<{a: number, b: string},{a: number}>({a: 2,b: 'a'},{a: 1}))

T extends U含义是,T中必须包含U中的所有属性,也就是U是T的子集。

在类中使用泛型

在类的声明中使用泛型

class State<T>{
    value: T;
    res: (val: T) => Array<T>;
}

let s = new State<number>();
s.value = 1
console.log(s)
s.res = <T>(val: T): Array<T> => {
    const len = 2;
    const res = [] as Array<T>;
    for(let i = 0;i < len;i++)
    {
        res[i] = val;
    }
    return res;
}
console.log(s.res(2))
  • TS的类的声明在编译后会留下额外代码 上面的TS代码编译后的js代码可以在下面看到
var State = /** @class */ (function () {
    function State() {
    }
    return State;
}());
var s = new State();
s.value = 1;
console.log(s);
s.res = function (val) {
    var len = 2;
    var res = [];
    for (var i = 0; i < len; i++) {
        res[i] = val;
    }
    return res;
};
console.log(s.res(2));

可以看到State标识符指向一个IIFE函数表达式,这个IIFE中声明了一个具名函数State,并被当成值return出来。因此,我们知道函数State的词法作用域是IIFE的内部作用域,当我们使用State函数创建了一个对象s之后,形成了一个对IIFE作用域的闭包。

在类的实现时使用泛型

class C<T>{
    value: T
    constructor(val: T){
        this.value = val
    }

    res(){
        console.log(this.value)
    }
}

let c1 = new C<number>(2)
console.log('c1',c1)

为泛型指定默认类型

class C<T = string>{
    value: T
    constructor(val: T){
        this.value = val
    }

    res(){
        console.log(this.value)
    }
}

let c1 = new C<number>(2)
console.log('c1',c1)

当TS无法从参数推断出类型且我们在书写代码时没有指定类型时,默认类型才会起作用。 因此,下面这种用法也不会报错。因为TS可以从参数推断出泛型类型为Number。

class C<T = string>{
    value: T
    constructor(val: T){
        this.value = val
    }

    res(){
        console.log(this.value)
    }
}

let c1 = new C(2)
console.log('c1',c1)