06Typescript中的泛型

软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

在像 C#和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
通俗理解:泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持(类型校验)。

泛型函数

下面来创建第一个使用泛型的例子:identity 函数。 这个函数会返回任何传入它的值。 你可以把这个函数当成是 echo 命令。

不用泛型的话,这个函数可能是下面这样:

1
2
3
4
5
// 传入/返回string类型的数据
function identity(value: string): string {
return value;
}
identity("test");

如果我们要同时返回 number 和 string 类型,可以使用 any 解决这个问题

1
2
3
4
5
6
// 传入/返回string类型的数据
function identity(value: any): any {
return value;
}
identity("test");
identity(123);

使用 any 类型会导致这个函数可以接收任何类型的 arg 参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。这就导致传入和返回的参数类型可能不一致

1
2
3
4
5
// 传入/返回string类型的数据
function identity(value: any): any {
return "呵呵呵";
}
identity(123);

因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,它是一种特殊的变量,只用于表示类型而不是值。

1
2
3
function identity<T>(arg: T): T {
return arg;
}

我们给 identity 添加了类型变量 T。 T 帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T 当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。

T 表示泛型,具体什么类型是调用这个方法的时候决定的。

1
2
3
4
5
6
7
8
9
10
11
function identity<T>(arg: T): T {
return arg;
}
console.log(identity<number>(123)); // 123
console.log(identity<string>("123")); // '123'
console.log(identity<boolean>(true)); // true
console.log(identity<object>({ age: 123 })); // {age:123}
console.log(identity<Array<number>>([1, 2, 3, 4])); // [1, 2, 3, 4]

// 当然我们没必要使用尖括号(<>)来明确地传入类型;编译器可以自动识别参数类型
console.log(identity([1, 2, 3, 4])); // [1, 2, 3, 4]

泛型接口

首先我先回顾一下函数类型接口的定义:

1
2
3
4
5
6
7
8
9
interface ConfigFn {
(value1: string, value2: string): string;
}

var setData: ConfigFn = function (value1: string, value2: string): string {
return value1 + value2;
};

console.log(setData("名字是", "张三")); //名字是张三

上面的列子参数我们只能传入 string 类型,如果需要传入其他类型,就需要用到泛型接口了。

1
2
3
4
5
6
7
8
9
10
11
// 第一种泛型接口
interface ConfigFn {
<T>(value: T): T;
}

var setData: ConfigFn = function <T>(value: T): T {
return value;
};

console.log(setData("张三")); //张三
console.log(setData(111)); //111

下面我们换一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface ConfigFn<T> {
(value: T): T;
}

function getData<T>(value: T): T {
return value;
}

var setData: ConfigFn<string> = getData;
console.log(setData("张三")); //张三

var setData2: ConfigFn<number> = getData;
console.log(setData2(111)); //111

泛型类

泛型类使用( <>)括起泛型类型,跟在类名后面。

基本用法

假如有个最小堆算法,需要同时支持返回数字和字符串 a - z 两种类型。通过类的泛型来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MinFun<T> {
public list: T[] = [];
add(value: T): void {
this.list.push(value);
}
min(): T {
let minNum = this.list[0];
this.list.forEach((item) => {
if (item < minNum) {
minNum = item;
}
});
return minNum;
}
}

var m1 = new MinFun<number>();
m1.add(11);
m1.add(9);
m1.add(5);
m1.add(4);
console.log(m1.min()); // 4

var m2 = new MinFun<string>();
m2.add("d");
m2.add("f");
m2.add("z");
m2.add("b");
console.log(m2.min()); // b

具体使用

定义一个 User 的类这个类的作用就是映射数据库字段。
然后定义一个 MysqlDb 的类这个类用于操作数据库。
然后把 User 类作为参数传入到 MysqlDb 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 定义一个User类 和数据库进行映射
class User {
username: string | undefined;
password: string | undefined;
}
// 2.定义操作数据库的泛型类
class MysqlDb<T> {
add(info: T): boolean {
console.log(info);
return true;
}
}
var u = new User();
u.username = "张三";
u.password = "123456";

// 3.把 User 类作为参数传入到 MysqlDb 中
var Db = new MysqlDb<User>();
Db.add(u); // User {username: '张三', password: '123456'}

下面我们增加一个 ArticleCate 类和数据库进行映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 1. 定义一个User类 和数据库进行映射
class User {
username: string | undefined;
password: string | undefined;
}
// 2.定义操作数据库的泛型类
class MysqlDb<T> {
add(info: T): boolean {
console.log(info);
return true;
}
updated(info: T, id: number): boolean {
console.log(info);
console.log(id);
return true;
}
}
var u = new User();
u.username = "张三";
u.password = "123456";

var Db = new MysqlDb<User>();
Db.add(u); // User {username: '张三', password: '123456'}

class ArticleCate {
title: string | undefined;
desc: string | undefined;
status: number | undefined;
constructor(params: {
title: string | undefined;
desc: string | undefined;
status?: number | undefined;
}) {
this.title = params.title;
this.desc = params.desc;
this.status = params.status;
}
}

//增加操作
var a = new ArticleCate({
title: "分类",
desc: "1111",
status: 1,
});

//类当做参数的泛型类
var Db2 = new MysqlDb<ArticleCate>();
Db2.add(a); // ArticleCate {title: '分类', status: 1}

//修改数据
var b = new ArticleCate({
title: "分类1",
desc: "描述",
});

a.status = 0;
Db2.updated(b, 2); //ArticleCate {title: '分类1', desc: '描述', status: undefined}---2

总结一下,这里主要体现把类作为参数类型的泛型类的用法。