# 泛型(Generics) <T>
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
# 泛型介绍
例:一个函数,接收一个参数,要求传入什么类型就返回什么类型。
// 第一种写法:固定某一种类型,不够灵活
let func = (value: number): number => {
return value
}
func(10) // 此函数只能用于 number,其他类型却需要重新定义。
// 第二种写法:使用 any ,无法满足要求。
let func2 = (value: any):any => {
return value.length
}
func2('str') // 接收到字符串,返回却是数字。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
使用 any 类型的弊端:无法控制返回类型和传入类型一致。比如传入 number 类型,使用者却有可能返回任意类型。
# 1. 泛型(就是表示类型的变量) <T>
用变量来表示类型,使用同一变量的值都必须是同一种类型。
用 <T>
定义类型变量 T
,变量 T
会捕获到传入的类型,就可以在之后使用这个类型。
// 定义泛型函数:普通函数写法
function func<T>(value: T): T{
return value
}
// 定义泛型函数:箭头函数写法
let func2 = <A>(value: A): A => {
return value
}
// 调用时可以传入类型参数给泛型,若不传程序会利用 类型推论,根据参数自动推论。
console.log(func<number>(10)) // 10
console.log(func('str')) // str
console.log(func2(20)) // 20
console.log(func2<string>('string')) // string
func2<string>(1) // 报错 Argument of type '1' is not assignable to parameter of type 'string'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# (2)使用泛型
同使用一般类型的用法基本一致
例:要求传入函数的参数是一个满足类型变量的数组,返回数组的长度
let getArrLength = <A>(value: A[]): number => { // value: Array<A> 另一种写法
return value.length
}
// 1. 传入所有的参数,包含类型参数(此处为 <string>):
console.log( getArrLength<string>(['1', '2', '3']) ) // 3
// 2. 利用类型推论 -- 编译器会根据传入的参数自动地帮助我们确定 <A> 中 A 的类型(此处推断为 number):
console.log( getArrLength([1, 2, 3, 4, 5]) ) // 5
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 多个泛型参数
// 普通函数写法
function multiParams <A, B, C> (tupleData: [C, B, A]): [B, C, A] {
return [tupleData[1], tupleData[0], tupleData[2]]
}
// 箭头函数写法
const multiParams2 = <A, B, C> (tupleData: [C, B, A]): [B, C, A] => {
return [tupleData[1], tupleData[0], tupleData[2]]
}
multiParams([1, false, 'str'])
/* 不指定泛型参数的类型,被推断为:
function multiParams<string, boolean, number>(
tupleData: [number, boolean, string]
): [boolean, number, string]
*/
multiParams2<number, boolean, string>(['str', false, 1])
/* 手动指定每个泛型参数的类型:
function multiParams<number, boolean, string>(
tupleData: [string, boolean, number]
): [boolean, string, number]
*/
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
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
# (3) 创建泛型函数
// 1. 定义变量,并直接赋值一个泛型函数,变量将被推论出类型
let func1 = <T>(arg: T): T => {
return arg
}
// 2. 先定义变量并指定为泛型函数类型,再赋值一个泛型函数给它
let func2: <T>(arg: T) => T = func1
// 3. 同上
let func3: <T>(arg: T) => T = <B>(value: B): B => {
return value
}
// 4. 使用带有调用签名的对象字面量来定义泛型函数
let func4: { <T>(arg: T): T } = func1
console.log( func1({a: 1, b: 2, c: 3}) ) // {a: 1, b: 2, c: 3}
console.log( func2([1, 2, 3]) ) // [1, 2, 3]
console.log( func3('str') ) // str
console.log( func4(['1', '2', '3']) ) // ["1", "2", "3"]
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
可以将以上的 4.
的对象字面量 拿出来作为一个接口:泛型接口
# (4)泛型接口
// 定义泛型接口
interface Fan {
<T>(arg: T): T
}
// 用泛型接口规定 变量 的类型,值必须满足泛型接口中规定的函数类型
let func1: Fan = <R>(value: R): R => {
return value
}
console.log( func1(1) ) // 1
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
优化:将泛型变量 T
当作整个接口的类型
// 将 T 作为接口的参数
interface Fan<T> {
(arg: T):T
}
// 使用接口时,必须指定泛型参数的类型
let func1: Fan<number> = <R>(value: R): R => {
return value
}
console.log( func1(1) ) // 1
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
除了泛型接口,还可以创建泛型类。 注意,无法创建泛型枚举和泛型命名空间。
# 5. 泛型类
class Person<T> {
constructor (public name: T) {
}
intro: () => T = ():T => {
return this.name
}
}
let tom = new Person<string>('tom')
console.log(tom) // {name: "tom", intro: ƒ}
console.log(tom.intro()) // tom
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
类有两部分:静态部分和实例部分,泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
# (6) 泛型约束
定义一个接口,在接口中规定泛型参数的约束条件,比如必须拥有某个属性,再使用这个接口和 extends
关键字来实现约束:
// 定义接口,约束条件为:必须包含 length 属性,且这个属性的值为 number 类型
interface HasLength {
length: number
}
// 定义类型变量 T 继承接口,T 将拥有接口的类型约束
function func<T extends HasLength>(arg: T): T {
console.log(arg.length)
return arg
}
func('string') // OK:参数是字符串,自带 length 属性
func([1, 2, 3]) // OK:参数是数组,自带 length 属性
func({a: 1, length: 5, b:2}) // OK:参数是对象,手动添加了length 属性
func(1) // 报错:参数是数字,没有 length 属性,不满足 HasLength 类型的约束条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 泛型约束 VS 接口约束
interface HasLength {
length: number
}
// 泛型约束: 泛型变量继承接口之后,再用于约束参数和返回值
function func1<T extends HasLength>(arg: T): T {
console.log(arg.length)
return arg
}
func1({a: 1, length: 5, b:2}) // OK,泛型约束只要至少包含约束的属性即可。
// 接口约束:直接使用接口来约束参数和返回值
function func2(arg: HasLength): HasLength {
console.log(arg.length)
return arg
}
func2({length: 5}) // OK
const obj = {a: 1, length: 5, b:2}
func2(obj) // OK
func2({a: 1, length: 5, b:2}) // 报错,接口约束的参数传入字面量对象时,要求属性不能多也不能少,必须和接口一样。
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
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
# 多个类型参数之间也可以互相约束
// 1. K extends keyof T:泛型 K 继承泛型 T 的属性,也就是要求参数 key 必须是参数 obj 的属性之一
// 2. T[K] 要求返回值必须是 obj 之中的某一属性值。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
let obj = { a: 1, b: 2, c: 3, d: 4 }
getProperty(obj, 'a'); // OK:obj 中包含 a 这个 key
getProperty(obj, 'm'); // 报错:obj 中不包含 m 这个 key
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 泛型参数的默认类型
在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
const Fan = <T = number>(value: T): T => {
return value
}
1
2
3
4
2
3
4
# (8) 在泛型里使用类类型
// 定义一个类类型,类类型接收一个泛型参数
interface Ctype<Z> {
new (): Z
}
// 使用类类型,并传入类型参数 T
function createFn<T>(c: Ctype<T>): T {
return new c();
}
// 若不想单独定义类类型接口 Ctype,函数参数中 'Ctype<T>' 也可直接写为类类型 '{ new (): T }' 或 'new () => T'
// create 函数的参数是一个 Class,返回值是这个 Class 的实例。
class Person {
constructor (public name: string = 'tom', public age: number = 20) {
}
}
let tom = createFn(Person)
console.log(tom) // {name: "tom", age: 20}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
更进一步用法:
interface Ctype<Z> {
new(n: string, a: number): Z
}
// 使用类类型,并传入类型参数 T
function createFn<T>(c: Ctype<T>, name: string, age: number): T {
return new c(name, age);
}
class Person {
constructor (public pname: string, public page: number) {
}
}
let tom = createFn(Person, 'tom', 20)
console.log(tom) // {pname: "tom", page: 20}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 使用原型属性推断并约束构造函数与类实例的关系
class Boy {
isBoy: boolean = true
}
class Girl {
dressing: string = 'skirt'
}
class Person {
legs: number = 2
}
class Primary extends Person {
boys: Boy = { // 使用 Boy 原型推断类型,约束 boys 的类型
isBoy: true
}
age: number = 10
}
class Middle extends Person {
girls: Girl = { // 使用 Girl 原型推断类型,约束 girls 的类型
dressing: 'skirt'
}
age:number = 13
}
function createInstance<A extends Person>(cla: new () => A): A {
return new cla()
}
let a = createInstance(Primary)
let b = createInstance(Middle)
console.log(a) // {legs: 2, boys: {isBoy: true}, age: 10}
console.log(b) // {legs: 2, girls: {dressing: "skirt"}, age: 13}
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
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