介绍
官网原话:软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能;
好吧,有点云里雾里,不大理解,简单的说就是TS中的类型强制性太高,比如
function identity(arg: number): number {
return arg;
}
如果我们这个函数要被复用,基本很难,因为类型完全限定死了,那如果把类型放开呢,比如any,那还写啥ts…js不香吗,受这罪…到这里,泛型的作用就体现了,它没有对类型限定死,但是又比any来的严格的多,下面我们具体看
一下;
泛型
我个人觉得泛型就是在定义的时候,定义了一个虚拟的类型,这个类型需要在被使用的时候才确认,使用的时候是string类型的,那么泛型就是string类型,使用的时候是number类型,那么泛型就是number类型;
而且,我一直认为TS其实就是在玩类型,花式玩类型,很多时候TS的类型注解和JS的代码混在一起,根本就看不懂到底写了个啥…所以任重而道远啊;
泛型简单的来说分为两种,一种叫做:函数泛型,另外一种就是类泛型
函数泛型
顾名思义,使用在函数中的泛型,先直接看个例子
function join<T>(first: T, second: T) {
return `${first}-${second}`;
}
通过<>这个符号来定义一个泛型,<>里面的是代表这个泛型的类型,这个类型可以是任意定义的,并不一定是上例中的T,这个T只是一个代指,具体是什么类型需要等到使用的时候确定,比如下例也是可以的:
function join<ABD>(first: ABD, second: ABD) {
return `${first}-${second}`;
}
等到使用的时候,只需要指定类型就可以了
// 正确
join<string>("1","2");
join<number>(1,2);
// 错误
join<string>(1,"2");
join<string>("1",2);
再看一个例子:
function join<ABD>(first: ABD[], second: Array<ABD>) {
return [...first,...second];
}
我们定义了一个这样的函数,它也是一个函数泛型,但是与之前的例子不同的是,它的两个参数是有类型ABD组成的数组
join<string>("123") // 报错
join<string>(["123"]) // 正确
当然,泛型可以写不止一个,再看一个例子,泛型可以写不止一个,多个也是可以的
function join<T,A>(first: T, second: A) {
return `${first}-${second}`;
}
join<string,number>("1",123) // 正确
// 甚至,TS是能根据定义的时候做类型推断的
join("1",123) // 正确,这么写也没有问题,TS能自己推断出具体类型是什么
可能有的同学看到这里会说,这个泛型确实比一般的类型复杂了点,但是也没上面说的复杂到看不懂啊,好吧,到这里其实只是一些基础知识,来着稍微复杂一点的试炼一下
const getData: <T>(str: T) => string = <T>(str: T): string => {
return `${str}-1`;
};
emmm…,第一反应肯定是有点懵,这是个啥,有点像箭头函数,但是为什么会有两个箭头函数在一起,好吧,我们具体解释一下:
首先我们确定这是一个函数表达式,然后后面应该是跟了一个箭头函数,然后返回的就是这个${str}-1
,当中一部分应该是TS的类型定义,那我换个写法,看看能不能理解:
// 将箭头函数拆解出来
function handleData<T>(str: T): string {
return `${str}-1`;
}
const getData: <T>(str: T) => string = handleData;
这样是不是好理解一点
// 函数名
const getData
// 函数的TS类型
<T>(str: T) => string
// 函数体
function handleData<T>(str: T): string {
return `${str}-1`;
}
// 对应到上面那个简写
// 函数名
const getData
// 函数的TS类型
<T>(str: T) => string
// 函数体
<T>(str: T): string => {
return `${str}-1`;
};
这样是不是能理解了,所以正如我上面所说,当TS的类型写法混入JS语法的时候,很容易让人就看不懂代码了,所以遇到这类代码必须静下心来好好分析,这类代码在一些框架中非常频繁的用到;
类泛型
泛类型就是在类中使用泛型,老规矩,还是直接看一个例子先:
class Join {
constructor(private data: string[]) {}
getData(index: number): string {
return this.data[index];
}
}
const data = new Join(["123", "456"]);
data.getData(0);
这个类代码初始化的时候接收一个由字符串组成的数组作为参数,代码本身没有问题,它的问题在于不够灵活,假如我们存储的数组中的类型不再单单是字符串类型,有可能是任何类型,并且返回的,那么,就必须写很长很长的联合类型,比如:
class Join {
constructor(private data: string[]|number[]|boolean[]) {}
getData(index: number): string|number|boolean {
return this.data[index];
}
}
const data = new Join(["123", "456"]);
data.getData(0);
很明显,这是不合理的,这种就可以用泛型改造,泛型的灵活性确实是TS中的一个亮点,那么泛型这么写,直接看例子吧,先看一个基础用法:
class Join<T> {
constructor(private data: T[]) {}
getData(index: number): T {
return this.data[index];
}
}
const data = new Join<string>(["123", "456"]);
data.getData(0);
和函数泛型意义,定义了一个T,并且存储的是由类型T组成的数组,通过getData返回的也是类型T,没有问题,那么如果是多个类型,也是同样的
class Join<T, P> {
constructor(private data: (T | P)[]) {}
getData(index: number): T | P {
return this.data[index];
}
}
const data = new Join<string, number>(["123", "456", 1, 2]);
data.getData(0);
到这里,我们基本说的都是基础类型,那么如果是对象这么弄,比如我们传入的是一个对象,并且确认每个对象上都有一个name属性
class Join<T> {
constructor(private data: T[]) {}
getData(index: number): T{
return this.data[index].name; // 报错,因为不能确定T上面存在name属性
}
}
那么此时可以这么改写:
interface User {
name: string;
}
class Join<T extends User> {
constructor(private data: T[]) {}
getData(index: number): T {
return this.data[index];
}
}
定义了一个接口(interface),然后让这个T去继承这个接口,那么我们就确认了传进来的每一个T上面都会存在一个name属性,如果使用的时候参数不对,那么Ts是会报错的
const data = new Join<string>(["123", "456"]); // 报错,因为string上不存在name属性
const data = new Join<User>([{ name: "oliver" }]); // 正确,类型换成了接口定义的类型
还有,还可以结束extends来对泛型进行约束,比如
interface User {
name: string;
}
class Join<T extends string | number> {
constructor(private data: T[]) {}
getData(index: number): T {
return this.data[index];
}
}
const data = new Join<User>([{ name: "oliver" }]); // 报错
const data = new Join<string>([{ name: "oliver" }]); // 正确
类Join的泛型T,通过extends将其约束为string或者为number,因此等到具体使用的时候,就只能是string或者number了;
小结
泛型就是一种虚拟类型,它的具体类型必须要等到这个泛型被使用的时候才确定,这样的好处就是它的用法会非常的灵活,而不是一开始就限定死了其变量类型,它能给予开发者非常高的自由度,加油,用好泛型,就是高手,一起努力吧!