泛型的作用是在运行时动态的指定变量的类型. 从而做到静态类型的最大代码复用.

泛型基本用法

没有使用泛型,代码会显的比较机械:不同类型的变量,虽然方法体一样,但我们还是会写两遍:

1
2
3
4
5
6
7
8
9
10
11
function operateNumber (a:number,b:number):number {
//xxxx
let c:number;
return c
}

function operateString(a:string, b:string):string {
//xxxx
let c:string;
return c
}

使用了泛型之后,我们可以做的代码复用了!

1
2
3
4
5
6
7
8
function operate<T>(a:T,b:T):T {
//xxx
let c:T
return c
}

operate(1,2)
operate('a','b')

复杂些的例子🌰:

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
59
// 没有泛型

class Queue {
private data = [];
push = item => this.data.push(item);
pop = () => this.data.shift();
}

const queue = new Queue();

queue.push(0);
queue.push('1'); // Oops,一个错误

// 一个使用者,走入了误区
console.log(queue.pop().toPrecision(1));
console.log(queue.pop().toPrecision(1)); // RUNTIME ERROR

class QueueNumber {
private data = [];
push = (item: number) => this.data.push(item);
pop = (): number => this.data.shift();
}

const queue = new QueueNumber();

queue.push(0);
queue.push('1'); // Error: 不能推入一个 `string` 类型,只能是 `number` 类型


// 使用了泛型
// 创建一个泛型类
class Queue<T> {
private data: T[] = [];
push = (item: T) => this.data.push(item);
pop = (): T | undefined => this.data.shift();
}

// 简单的使用
const queue = new Queue<number>();
queue.push(0);
queue.push('1'); // Error:不能推入一个 `string`,只有 number 类型被允许

function reverse<T>(items: T[]): T[] {
const toreturn = [];
for (let i = items.length - 1; i >= 0; i--) {
toreturn.push(items[i]);
}
return toreturn;
}

const sample = [1, 2, 3];
let reversed = reverse(sample);

reversed[0] = '1'; // Error
reversed = ['1', '2']; // Error

reversed[0] = 1; // ok
reversed = [1, 2]; // ok

泛型约束

泛型约束,就是为没有确定的类型制定一定会具备的特性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
// 我们制定 T 类型一定会 拥有 length 属性. 则可以这样写:
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3});

在泛型约束中使用类型参数

你可以声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象obj上,因此我们需要在这两个类型之间使用约束。

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'.

在泛型里使用类类型

在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型

1
2
3
function create<T>(c: {new(): T; }): T {
return new c();
}

一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。

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
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!

泛型类

泛型常用于 class中,用以约束说明 变量类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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; };

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

alert(stringNumeric.add(stringNumeric.zeroValue, "test"));

高级用法

extends继承

泛型中可以用 extends 继承(约束)某个规定的类型.其他约束的作用.

1
2
3
function myGenericFunction<T extends string>(arg: T): T {
return arg;
}

条件类型

将泛型作为三元运算符的条件:

1
type MyType<T> = T extends string ? boolean : number;

映射类型

keyof 可以用于声明泛型的key, 从而实现对类型的编程效果.

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
// 全键可选
type Partial<T> = {
[P in keyof T]?: T[P];
};

// 全键必需
type Required<T> = {
[P in keyof T]-?: T[P];
};

// 全键只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 可为空类型
type Nullable<T> {
[P in keyof T]: T[P] | null
}

// 包装一个类型的属性
type Proxy<T> = {
get(): T
set(value: T): void
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>
}
function proxify(o: T): Proxify<T> {
// ...
}
let proxyProps = proxify(props)