1. 泛型介绍
它的出现主要是为了解决参数与返回值类型之间的关系,比如一个函数接受一个类型的参数,并返回该参数,如果类型特别多就会很难写,可能会用到any,因为any可以代表任意类型的值,可也就相当于关闭了ts校验,此时泛型出现了,通过先定义一个占位(类型参数),等真正调用函数时,传入不同的类型,就代表函数的参数类型以及返回值类型。
泛型的特点就是带有类型参数。
如何定义?在函数名后使用尖括号<T>
,尖括号里存放类型参数,可以为多个参数,等到调用时传入具体的类型即可,就代表该函数参数的类型是什么。这里的T是随便起的名字,只是一个占位,起什么名字都行,默认这个名字的首字母大写。
这里传入了number类型,正常情况下函数调用时就可以省略不写类型参数的值,ts会自动推断,当推断不出来时会报错,需要自己写入类型参数的值。
function getFirst<T>(arr:T[]):T {
return arr[0];
}
getFirst<number>([1, 2, 3]);
getFirst([1, 2, 3]);
2. 写法
1. 函数的泛型写法
对于函数声明(也就是function声明的函数),尖括号放在函数名后面,尖括号里面存放类型参数。
function id<T>(arg:T):T {
return arg;
}
对于变量定义的函数
写法一:在变量名后+ :
+尖括号,尖括号里存放类型参数
写法二:使用对象形式:
// 写法一
let myId:<T>(arg:T) => T = id;
// 写法二
let myId:{ <T>(arg:T): T } = id;
2. 接口的泛型写法
写法一:直接在接口名后加尖括号。
interface Box<Type> {
contents: Type;
}
let box:Box<string>;
写法二:定义在某个方法中
interface Fn {
<Type>(arg:Type): Type;
}
function id<Type>(arg:Type): Type {
return arg;
}
let myId:Fn = id;
写法一与写法二的不同点:写法一将类型参数之间写在外部,整个接口内部的属性和方法都能使用,而写法二是具体到某个方法上了,只能供自己使用。
3. 类的泛型写法
直接在类名后加尖括号即可,也可以用在类的表达式中,因为js中的类本质就是一个构造函数,所以泛型类也可以写成构造函数形式。
泛型类描述的是类的实例,而静态属性和静态方法只能定义在类的本身,所以这两者不能引用类型参数,否则会报错。
class Pair<K, V> {
key: K;
value: V;
}
type MyClass<T> = new (...args: any[]) => T;
4. 类型别名(type)的泛型写法
type也能使用泛型。
type Nullable<T> = T | undefined | null;
3. 类型参数的默认值
类型参数可以设置一个默认值,假如使用时,没有给出类型参数,ts会先自行推断,推断不出的使用默认值。
function getFirst<T = string>(
arr:T[]
):T {
return arr[0];
}
一旦类型参数有默认值,就表示它是可选参数。如果有多个类型参数,可选参数必须在必选参数之后。
<T = boolean, U> // 错误
<T, U = boolean> // 正确
4. 数组的泛型表示
使用ts原生的类型接口Array<T>
在ts中Array是一个泛型接口,基础实现如下
interface Array<Type> {
length: number;
pop(): Type|undefined;
push(...items:Type[]): number;
// ...
}
像ts中的Map、Set和Promise都是泛型接口,完整写法是Map<K, V>、Set<T>和Promise<T>
只读数组泛型:
ReadonlyArray<T>
:数组成员是T类型,个别成员是这个类型
Readonly<T[]>:整个数组都是T[]类型,全部都是这个类型
5. 类型参数的约束条件
有时候对于传入的类型,需要在代码内进行判断是否满足某种情况,这时可以再设置类型参数时进行判断是否满足某种情况。
判断格式:
TypeParameter表示类型参数,extends是关键字,ConstraintType表示类型参数要满足的条件,就相当于类型参数是ConstraintType的子类即可。
<TypeParameter extends ConstraintType>
类型参数可以同时设置约束条件和默认值,但前提必须默认值满足约束条件
type Fn<A extends string, B extends string = 'world'>
= [A, B];
type Result = Fn<'hello'> // ["hello", "world"]
泛型本质上是一个类型函数,通过输入参数,获得结果,两者是一一对应关系。
约束条件不能约束自己。
6. 使用注意点
- 尽量少用泛型
虽然泛型很灵活,但是会加大代码的复杂性,使其变得难读难写。
- 类型参数越少越好
多一个类型参数,多一道替换步骤,加大复杂性。
- 类型参数需要出现两次,如果在定义后只出现一次,不如不要
function greet<Str extends string>(
s:Str
) {
console.log('Hello, ' + s);
}
编译后,就只在定义参数时出现。
function greet(s:string) {
console.log('Hello, ' + s);
}
- 泛型可以嵌套,类型参数可以是另一个泛型
type OrNull<Type> = Type|null;
type OneOrMany<Type> = Type|Type[];
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;