持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
泛型函数
泛型: 是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
定义泛型函数
正常声明一个变量的时候,像是下面这样。
const a: string = "kong";
复制代码
而泛型,是在使用的时候才会指定类型(一般用大写的T
做变量,Type的简写,用其他的也都没问题),如下:
function fn1<T>(value: T): T {
return value;
}
fn1<string>("kong");
fn1(1);
复制代码
上面的函数fn1
添加了类型变量 T
,T
能够捕获到用户传入的参数类型,然后再根据T
做函数返回值的类型。
调用的时候可以<>
手动设定类型,也可以不设置让ts自动进行类型推导。
- 默认类型
声明函数的时候,可以传给T
一个默认类型。调用函数的时候,不传递类型就是默认类型。
function fn1<T = number>(value: T): T {
return value;
}
复制代码
- 多个类型参数
定义泛型的时候,可以一次性定义多个类型参数
function swap<T, U>(arr: [T, U]): [U, T] {
return [arr[1], arr[0]]
}
swap([100, "haha"]); // ["haha", 100]
复制代码
类型约束
在函数内部使用泛型的变量的时候,由于还不知道它属于哪种类型,所以不能随意操作变量的属性和方法。
如下: value.length
会出现错误信息 类型T上不存在属性length
function fn2<T>(value: T){
value.length // 错误:类型T上不存在属性length
}
复制代码
这时候就可以使用 类型约束,关键字 extends
。
interface HasLength {
length: number
}
function fn3<T extends HasLength>(value: T) {
value.length
}
复制代码
上述代码,让泛型T
继承自Haslength
接口,表明传入的参数要遵守 HasLength
接口的规则,也就是必须要含有length
属性。
也可以直接这样继承:
function fn3<T extends {length: number}>(value: T) {
value.length
}
复制代码
此时,如果传入不包含length
属性的参数,就会在编译阶段提示错误:
fn3([1,2,3]); // 正确
fn3("KONG"); // 正确
fn3(100); // 错误
复制代码
提示错误: 类型number的参数不能赋值给类型HasLength的参数。
使用泛型编写一个好的函数要注意的点
- 可能的情况下,使用类型参数本身,而不是对其进行约束
- 尽可能少的使用类型参数
- 如果一个类型的参数只出现在一个地方,请重新考虑是否真的需要它
泛型接口
定义一个标准的接口,应该像下面这样:
interface Person {
name: string,
age: number
}
const p1: Person = {
name: "tom",
age: 18
}
复制代码
- 简单使用
上面接口里面的类型是固定写死的,如果需要由用户传入接口的类型,那就需要使用泛型来定义接口:
interface Person2<T1, T2> {
name: T1,
age: T2,
}
const p2: Person2<string, number> = {
name: "jack",
age: 18
}
复制代码
上面的例子是在使用的时候传入了类型,也可以在定义接口的时候给泛型默认值:
interface Person2<T1 = string, T2 = number> {
name: T1,
age: T2,
}
复制代码
- 使用泛型接口的方式定义函数需要符合的形状
interface CreateArr {
<T>(length: number, value: T): Array<T>
}
let createArrFn: CreateArr;
createArrFn = function <T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
复制代码
上面的接口,使用泛型定义了一个函数。然后创建了createArrFn方法
,实现了这个泛型接口的函数。可以传入两个参数length
和value
,其中length
为数组的长度所以是number
类型,value
类型为泛型,根据调用方法时传入参数的类型决定。最终这个方法会返回一个长度为length
的数组。
泛型类
- 在类名后面定义泛型
T
,类里面使用
class GenAny<T> {
value: T;
add: (x: T, y: T) => T;
}
复制代码
const myGen = new GenAny<number>();
myGen.value = 100;
myGen.add = (x, y) => x + y;
// 调用
myGen.add(3, 4);
复制代码
- 应用: 定义一个
Person
类
class Person<T1, T2> {
name: T1;
age: T2;
sex: T1;
constructor(name: T1, age: T2, sex: T1){
this.name = name;
this.age = age;
this.sex = sex;
}
}
复制代码
上面的Person
类包含了泛型T1
T2
,name age sex
属性。
实例化:
- 自动类型推导,可以不传入参数的类型:
const p1 = new Person("tom", 18, "boy");
复制代码
自动推断出T1
为string
类型,T2
为 number
类型。此时如果第三个参数sex传入非string类型就会提示错误,以为已经把T1
自动推导为string
类型了。
- 不用类型推导就要在
new
实例化的时候传入类型
const p2 = new Person<string, string>("jack", "18", "boy");
复制代码
- 使用类来约束对象并传递类型
const p3: Person<string, number> = new Person("jack", 18, "boy");
复制代码
这时候可以发现上一节中用Array
类来声明数组的写法和使用泛型类约束变量的写法是很相似的:
const arr: Array<number> = [2, 4, 6];
复制代码