大白话之 ts 的泛型
2023-07-07 00:24:12 # fontend

刚开始接触 ts 的时候,我以为泛型就是 any 类型,直到某天在使用时想如何才能让输入参数和输出的值类型一致,再回看指南文档,才发现这两个可是不一样呢。

any 类型其实就是不约束类型,写了就相当于没写。
但是泛型不一样,它的功能更强大。

1. 约束返回值的类型与传入参数的类型是相同的。

1
2
3
4
5
6
7
8
// <T> 可以捕获用户的传入的类型
function identity<T>(arg:T):T {
return arg;
}

// 使用此函数的两种方法
1. let output = identity<string>('hello'); // 明确定义输入参数类型,输出的值也将会是 ‘string’
2. let output = identity('hello'); // 利用 类型推论 - 即编译器会根据传入的参数自动帮助确定 T 的类型,

说明: 建议使用第2种方法,如果编译器不能自动推论,再使用第1种方法。

2. 在函数体中正确使用泛型变量

1
2
3
4
function identity<T>(arg:T):T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}

泛型确实可以是任何类型或者自定义类型,但是如果没有明确定义的属性,在函数体中使用是会报错的,就像上面一样,此时该如何解决呢?

1
2
3
4
5
6
7
8
9
10
// 1. 
function identity<T>(arg:T[]):T[] {
console.log(arg.length);
return arg;
}
// 2.
function identity<T>(arg:Array<T>):Array<T> {
console.log(arg.length);
return arg;
}

如果要使用变量的相应属性,变量类型也必须能通过类型校验,否则编译阶段就会报错。

3. 除了可以定义泛型接口,还可以创建泛型类

注意: 泛型不能创建泛型枚举和泛型命名空间

1
2
3
4
5
6
7
8
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

将泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。类分两部分:静态部分和实例部分,泛型类指的是实例部分,静态属性不能使用泛型类型。

4. 泛型约束

比如上述第一个里面的泛型类型,想访问 .length 属性,那就必须要传入一个确定包含此属性的类型,就能允许访问,根据此需求可以列出对 T 的约束要求。在泛型中通过 extends 关键字实现约束:

1
2
3
4
5
6
7
8
interface Lengthwise {
length: number;
}

function identity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // no more error
return arg;
}

现在这个泛型函数被定义了约束,所以参数不再是任意类型:

1
2
identity(3) // Error, number doesn't have a .length property
identity({length:1, value: 3}); // no error

在泛型约束中,还可以使用类型参数:

声明一个类型参数,且它被另一个类型参数所约束。

1
2
3
4
5
6
7
8
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

5. 更高级的使用:在泛型中使用类类型

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
// 创建工厂函数,引用构造函数的类类型
function create<T>(c: {new(): T; }): T {
return new c();
}

// 使用原型属性推断并约束构造函数与类实例的关系
class BeeKeeper {
hasMask: boolean;
}

class ZooKeeper {
nametag: string;
}

class Animal {
numLegs: number;
}

class Bee extends Animal {
keeper: BeeKeeper;
}

class Lion extends Animal {
keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}

createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!