入个TypeScript的门(8)——泛型

61 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

前面我们说了为了实现我们组件能适用更多的环境,于是有了联合类型,但是联合类型它只能解决当前的问题,并不能预测未来的问题,也就是不太能在未来也实现复用,于是就有了泛型的概念。

泛型

泛型就是让我们的代码有更好的复用性,它的使用就是在<>中填入一个参数,然后在需要使用的地方用这个参数替换即可:

function Fn<T>(x:T):T {
  return x;
}

这样我们就可以在调用的时候,根据我们的需求再去指定传递的参数类型:

function Fn<T>(x:T):T {
  return x;
}
let res = <number>Fn(1);
let res2 = <string>Fn('xxx');
-----------------或者让TS自己进行类型推断
let res = Fn(1);

这就十分类似我们的函数,我们的函数也是为了将可能会重用复用的逻辑进行封装,然后减少代码的编写量,所以有了形参。而泛型也类似,<T>的T就像形参,而我们在调用的时候传递的<number>的number就类似实参。

类型别名和接口加上泛型

泛型还可以用于泛型别名:

type Fn = <T>(arg: T) => T;
const printFn: Fn = (arg) => {
  return arg;
};

或者接口上面也可以用泛型:

interface Fn<T> {
  (arg: T): T;
}
function printFn<T>(arg:T):T{
  return arg;
}
const fn:Fn<number> = printFn;

泛型约束

但是使用泛型会导致一个问题,那就是我们的类型变成了一种新的,也就是未知的类型,这时候我们对其进行的操作就可能会有一些问题,比如:

function printFn<T>(arg:T):T{
  return arg.length;  //报错:类型“T”上不存在属性“length”。
}

这时候我们就得对其进行泛型约束,告诉编译器我们的泛型有哪些东西:

interface Lengthwise {
  length: number;
}
function printFn<T extends Lengthwise >(arg:T):T{
  console.log(arg.length);
  return arg; 
}

这时候我们的T类型就有了length属性了,我们就可以得到length属性了。

但是有时候,这个约束也很烦,比如:

function add<T>(x:T,y:T):T {
  return x+y;  //报错:运算符“+”不能应用于类型“T”和“T”。
}
--------
function add<T extends number>(x:T,y:T):T {
  let res =  x+y;  
  return res;  //报错:不能将类型“number”分配给类型“T”。
}

那么现在我们的x和y的类型就成了number的子类型了,那么x+y就会返回一个新的number类型的值,而不再是T类型,而我们返回的还得是T类型,于是就到使用类型断言才行。

function add<T extends number>(x:T,y:T):T {
  let res =  x+y;  
  return res as T;  
}

至于什么时候可以用类型断言可以在我之前写的那里看,就是A 兼容 B那么A与B就可以相互进行断言。

泛型类

泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。

class Person<T> {
  name: T;
  constructor(name:T) {
    this.name = name;
  }
  add!:(x: T, y: T)=> T;
}

!为非空断言, 否则报错:属性“XXX”没有初始化表达式,且未在构造函数中明确赋值。

这里之所以能对类进行泛型,我想主要是为了类还可以创建一个类型而不仅仅是一个类,所以才有泛型。所以在泛型类中我们只能对实例属性和方法使用泛型,而不能对静态方法和属性进行泛型。

class Person<T> {
  name!: T;
  static age:T;  //报错:静态成员不能引用类类型参数。
  static getName!:(x:T):void;  //报错:静态成员不能引用类类型参数。
  add!:(x: T, y: T)=> T; 
}

泛型的默认值

我们函数的参数都有默认值,那么我们的泛型也有默认值:

class Person<T=string> {
  name!: T;
  add!:(x: T, y: T)=> T; 
}

let p:Person = {name:1223,add(x,y){return x}}; //报错:不能将类型“number”分配给类型“string”。

那么当我们没有传递类型的时候,就会默认将T类型认为是默认类型。这时候我们就不能让编制器进行推断了,而必选自己传递类型参数了。

class Person<T=string> {
  name!: T;
  add!:(x: T, y: T)=> T; 
}

let p:Person<number> = {name:1223,add(x,y){return x}}; 

可以声明多个泛型

我们的泛型不是只可以声明一个,而是可以声明多个,然后按照函数参数那样进行赋值:

function add<T,U>(x:T,y:U):U {
  console.log(x,y);
  return y;
}

add<number,string>(3,'x');

以上就是泛型的基本知识了,我泛型用得挺少了,也就只懂得这些基础知识....