Appearance
泛型的类 / 接口 / 函数
背景:很多时候,我们希望一个函数或者类可以支持多种数据类型 栗子, 一个打印函数的改造:函数会返回任何传入它的值
typescript
function log(value: string): string {
console.log(value);
return value;
}
1
2
3
4
2
3
4
改造为接受一个数组参数
方式一:函数重载
typescript
function log(value: string): string;
function log(value: string[]): string[];
function log(value: any) {
console.log(value);
return value;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
方式二:联合类型
typescript
function log(value: string | string[]): string | string[] {
return value;
}
1
2
3
2
3
希望 log 函数可以接受任意类型的参数
方式一:any 类型
但是,丢失了类型之间的约束关系,忽略了参数类型与函数的返回值类型必须是一致的
typescript
function log(value: any): any {
console.log(value);
return value;
}
1
2
3
4
2
3
4
方式二:泛型
- 通过泛型来定义函数 - 泛型函数
概念:不预先确定数据类型,具体的类型在使用的时候才能确认
typescript
function log<T>(value: T): T {
return value;
}
//调用方式一: 在调用的时候 指定 T的类型
console.log(
log<string[]>(["a", "b"]),
);
// 调用方式二: 利用TS的类型推断,省略 函数的参数类型 --- 推荐
console.log(log(["a", "b"]));
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 通过泛型来定义函数类型 -- 泛型函数类型
泛型可以定义一个函数,也可以定义一个函数类型
typescript
function log<T>(value: T): T {
return value;
}
/*
类型别名-------------
使用类型别名定义一个泛型函数类型,
等号后面跟函数签名差不多, 但是要把函数的名称去掉
*/
type Log = <T>(value: T) => T;
// 泛型函数实现
let myLog: Log = log;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
或
typescript
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
let myIdentity1: <U>(arg: U) => U = identity;
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
泛型接口
在这里,泛型仅仅约束了一个函数,但我们也可以用泛型来约束接口的其他成员
typescript
// 和类型别名的方式是完全等价的
interface Log {
<T>(value: T): T;
}
1
2
3
4
2
3
4
用泛型来约束接口的其他成员:把泛型放在接口名称的后面,这样接口的所有成员都能受到泛型的约束了
typescript
interface Log<T> {
(value: T): T;
}
1
2
3
2
3
注:泛型变量约束了整个接口后,在实现的时候,必须指定一个类型
typescript
function log<T>(value: T): T {
return value;
}
interface Log<T> {
(value: T): T;
}
// 错误提示: 泛型类型“Log<T>”需要 1 个类型参数。ts(2314)
let myLog: Log = log;
// 解决
let myLog1: Log<number> = log;
// myLog1的参数只能是 number
myLog1(1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
也可以在接口的定义中指定一个默认类型
typescript
// 指定默认类型
interface Log<T = Array<string>> {
(value: T): T;
}
let myLog: Log = log;
myLog(["1", "2"]);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
泛型也可以约束类的成员
泛型类
把泛型变量放在类的名称后面,就可以约束所有类的成员了 注意:泛型不能约束类的静态成员
typescript
class Test<K> {
// 错误提示: 静态成员不能引用类类型参数。ts(2302)
// static eat(param:T){
// }
run(value: K) {
return value;
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
实例的方法将会受到泛型的约束
typescript
// 实例化
let log1 = new Test<number>();
// 错误提示: 类型“"a"”的参数不能赋给类型“number”的参数。ts(2345)
// log1.run('a')
log1.run(1);
1
2
3
4
5
6
7
2
3
4
5
6
7
实例化时可以不传入类型参数 当没有指定参数时, 实例方法的参数类型可以是任意的
typescript
let log2 = new Test();
log2.run("a");
log2.run(1);
1
2
3
4
5
6
2
3
4
5
6
泛型约束
typescript
function testA<T>(value: T): T {
// 类型“T”上不存在属性“length”。ts(2339)
console.log(value, value.length);
return value;
}
1
2
3
4
5
6
2
3
4
5
6
解决:T 继承 Length 接口,表示 T 受到了约束,即输入的参数必须具有 length 属性
typescript
interface Length {
length: number;
}
function testA<T extends Length>(value: T): T {
console.log(value, value.length);
return value;
}
testA([1]);
testA("12212");
testA({ length: 1 });
/*
错误提示:
类型“{ a: number; }”的参数不能赋给类型“Length”的参数。
对象文字可以只指定已知属性,并且“a”不在类型“Length”中。ts(2345)
*/
// testA({a:1})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
泛型的好处
- 函数和类可以轻松地支持多种类型,增强程序的扩展性
- 不必写多条函数重载,冗长的二联合类型声明,增强代码可读性
- 灵活控制类型之间的约束