最近上手TS,泛型是我一直迷糊的点,官网泛型的部分反反复复看了好几遍,讲解泛型的文章也看了不少,好像是懂了,又好像不懂。
如果问我泛型是什么,嗯,支持多类型数据?传入类型影响输出类型?理解成更严格的any?带<>?如果你的回答和我一样,恭喜你,你已经达到了你以为你懂,实战就蒙的境界~(不信,去找个开源TS项目的源码看看。)
今天终于搞明白泛型了,写一个笔记,文章有点长,如果你也是TS小白,强烈建议看一下,因为我也是小白,我懂你~如果你是大佬,点关闭~哈哈哈哈哈
一、值和类型 *
泛型是一种抽象类型,string,number,boolean等是一种具体类型,具体类型我们多是对值进行编程,泛型是对类型进行编程。
这句话听起来抽象,但是最终我明白了泛型,也是因为这句话。
1、对值编程
- TS中我们常用的就是对值限定类型。从集合论的角度上来说, 值的集合就是类型。
- 下面的例子,可以看出是对具体的值 ‘lianlian’ 进行编程,字符串 ‘lianlian’ 就是 string 类型的一个具体的值 。所以,这就是我们常用的对值限定类型,对值进行的编程。
function sayName(name: string) {
console.log(`my name is ${name}`)
}
sayName('lianlian')
二、泛型
上面说了很多,我们了解了类型和值的区别。也了解了我们平时使用的简单类型,本质上都是对值进行编程,只是在编程之前,对将值限定了类型~
1、为什么需要泛型
想实现一个identity函数,这个函数会返回任何传入它的值(肯定传入和返回的类型也是相同)。(官网上你一定看过这个函数例子~)。
可以使用any这样写,但是这样写会丢失一些信息:传入的类型和返回的类型要求是相同的。如果我们传入一个’lianlian’,只知道任何类型的值都有可能被返回。
function identity(arg: any): any { return arg }
可以这样写,这样就可以保证传入的类型和返回的类型是一致的,但是写法是不是太笨了?JS提供了多少种类型,我们就要复制多少遍。
function identityNumber(arg: number): number { return arg }
function identityString(arg: string): string { return arg }
......
所以为了解决上面的问题,我们就需要泛型
。此时 T 是一个抽象类型,只有在调用方法的时候,才能确定类型。所以当我们传入参数‘lianlian’时,T表示了string类型。
<T>
:函数声明一个表示类型的变量(所以此处T,U等其他字母都可以)(arg: T)
:函数传入的参数是 T 类型.(): T
:函数的返回值也是 T 类型.
function identity<T>(arg: T): T { return arg }
identity('lianlian')
2、泛型是对类型的编程
这句话的意思呢?看下面的栗子🌰~
开始我定义了一个Person 类,有三个属性,都是必填选项,用于用户注册。
enum Sex {
male,
female,
unknow
}
type Persion = {
name: string,
sex: Sex,
age: number
}
突然新增一个组件用于开展活动,也要用到Person类,要求是原有的属性变为可选。怎么办?
- 重新写一个AvtivityPerson 类
- 利用泛型,其中我利用了一个TS内置的类型别名(工具类)
Partial<T>
,Partial作用是让 T 中的所有属性都是可选的。
/**
* TS 源码
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 重写类
type AvtivityPerson = {
name?: string,
sex?: Sex,
age?: number,
}
// 利用泛型
type AvtivityPerson = Partial<Persion>
看图,泛型是不是和普通函数很相似(定义和使用)?普通函数操作的是值,而泛型是将类型看成值进行操作。可以看出泛型函数是接收一个类型作为参数,最后返回一个类型。
三、泛型的种类
上面介绍泛型的使用时,示例中泛型的用法时泛型函数
,泛型还包括泛型类
和泛型接口
。用法和泛型函数
// 泛型接口
interface CheckBoxGripProps<T> {
value: T[]
disabled?: boolean
}
// 泛型类
export default class CheckboxGrid extends tsx.Component<CheckBoxGripProps<T>>
四、泛型约束
我们可以对函数的参数进行约束,就像对 sayName 函数的形参进行类型约束,使得函数只能接收 string 类型的参数一样,泛型也可以进行约束
。
举例,函数中想打印参数的size,但是参数类型中没有size属性,所以会报错,解决办法就是限定传给trace函数的参数类型中必须有size属性!使用关键字extends就可以实现。
// 报错的
function trace<T>(arg: T): T {
console.log(arg.size) // 类型“T”上不存在属性“size”
return arg
}
// 泛型约束
interface Size {
size: number
}
function trace<T extends Size>(arg: T): T {
console.log(arg.size)
return arg
}
五、什么时候使用泛型
看完上面的例子,当你的接口、函数、类有如下使用情况时,可以使用泛型:
- 参数是多类型时,比如 identity 函数的泛型声明。
- 多处使用时,比如 Partial 泛型。
最后:
感谢下面的文档,都是我学泛型的时候看的~