|
| 1 | +# 泛型 |
| 2 | + |
| 3 | +泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。 |
| 4 | + |
| 5 | +## 简单的例子 |
| 6 | + |
| 7 | +首先,我们来实现一个函数 `createArray`,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值: |
| 8 | + |
| 9 | +```ts |
| 10 | +function createArray(length: number, value: any): Array<any> { |
| 11 | + let result = []; |
| 12 | + for (let i = 0; i < length; i++) { |
| 13 | + result[i] = value; |
| 14 | + } |
| 15 | + return result; |
| 16 | +} |
| 17 | + |
| 18 | +createArray(3, 'x'); // ['x', 'x', 'x'] |
| 19 | +``` |
| 20 | + |
| 21 | +上例中,我们使用了[之前提到过的数组泛型](../basics/type-of-array.md#数组泛型)来定义返回值的类型。 |
| 22 | + |
| 23 | +这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型: |
| 24 | + |
| 25 | +`Array<any>` 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 `value` 的类型。 |
| 26 | + |
| 27 | +这时候,泛型就派上用场了: |
| 28 | + |
| 29 | +```ts |
| 30 | +function createArray<T>(length: number, value: T): Array<T> { |
| 31 | + let result = []; |
| 32 | + for (let i = 0; i < length; i++) { |
| 33 | + result[i] = value; |
| 34 | + } |
| 35 | + return result; |
| 36 | +} |
| 37 | + |
| 38 | +createArray<string>(3, 'x'); // ['x', 'x', 'x'] |
| 39 | +``` |
| 40 | + |
| 41 | +上例中,我们在函数名后添加了 `<T>`,其中 `T` 用来指代任意输入的类型,在后面的输入 `value: T` 和输出 `Array<T>` 中即可使用了。 |
| 42 | + |
| 43 | +接着在调用的时候,可以指定它具体的类型为 `string`。当然,也可以不手动指定,而让类型推论自动推算出来: |
| 44 | + |
| 45 | +```ts |
| 46 | +function createArray<T>(length: number, value: T): Array<T> { |
| 47 | + let result = []; |
| 48 | + for (let i = 0; i < length; i++) { |
| 49 | + result[i] = value; |
| 50 | + } |
| 51 | + return result; |
| 52 | +} |
| 53 | + |
| 54 | +createArray(3, 'x'); // ['x', 'x', 'x'] |
| 55 | +``` |
| 56 | + |
| 57 | +## 多个类型参数 |
| 58 | + |
| 59 | +定义泛型的时候,可以一次定义多个类型参数: |
| 60 | + |
| 61 | +```ts |
| 62 | +function swap<T, U>(tuple: [T, U]): [U, T] { |
| 63 | + return [tuple[1], tuple[0]]; |
| 64 | +} |
| 65 | + |
| 66 | +swap([7, 'seven']); // ['seven', 7] |
| 67 | +``` |
| 68 | + |
| 69 | +上例中,我们定义了一个 `swap` 函数,用来交换输入的元组。 |
| 70 | + |
| 71 | +## 泛型约束 |
| 72 | + |
| 73 | +在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法: |
| 74 | + |
| 75 | +```ts |
| 76 | +function loggingIdentity<T>(arg: T): T { |
| 77 | + console.log(arg.length); |
| 78 | + return arg; |
| 79 | +} |
| 80 | + |
| 81 | +// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'. |
| 82 | +``` |
| 83 | + |
| 84 | +上例中,泛型 `T` 不一定包含属性 `length`,所以编译的时候报错了。 |
| 85 | + |
| 86 | +这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 `length` 属性的变量。这就是泛型约束: |
| 87 | + |
| 88 | +```ts |
| 89 | +interface Lengthwise { |
| 90 | + length: number; |
| 91 | +} |
| 92 | + |
| 93 | +function loggingIdentity<T extends Lengthwise>(arg: T): T { |
| 94 | + console.log(arg.length); |
| 95 | + return arg; |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +上例中,我们使用了 `extends` 约束了泛型 `T` 必须符合接口 `Lengthwise` 的形状,也就是必须包含 `length` 属性。 |
| 100 | + |
| 101 | +此时如果调用 `loggingIdentity` 的时候,传入的 `arg` 不包含 `length`,那么在编译阶段就会报错了: |
| 102 | + |
| 103 | +```ts |
| 104 | +interface Lengthwise { |
| 105 | + length: number; |
| 106 | +} |
| 107 | + |
| 108 | +function loggingIdentity<T extends Lengthwise>(arg: T): T { |
| 109 | + console.log(arg.length); |
| 110 | + return arg; |
| 111 | +} |
| 112 | + |
| 113 | +loggingIdentity(7); |
| 114 | + |
| 115 | +// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'. |
| 116 | +``` |
| 117 | + |
| 118 | +多个类型参数之间也可以互相约束: |
| 119 | + |
| 120 | +```ts |
| 121 | +TBD |
| 122 | +``` |
| 123 | + |
| 124 | +## 泛型接口 |
| 125 | + |
| 126 | +TBD |
| 127 | + |
| 128 | +## 泛型类 |
| 129 | + |
| 130 | +TBD |
| 131 | + |
| 132 | +## 参考 |
| 133 | + |
| 134 | +- [Handbook - Generics](http://www.typescriptlang.org/docs/handbook/generics.html) | [中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/generics.html) |
| 135 | + |
| 136 | +--- |
0 commit comments