目录
- 不用泛型的话,这个函数可能是下面这样:
- function identity(arg: number): number
- {
- return arg;
- }
-
- 或者,使用any类型来定义函数:
- function identity(arg: any): any
- {
- return arg;
- }
-
- 使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:
- 传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
-
- 因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,
- 它是一种特殊的变量,只用于表示类型而不是值。
-
- function identity
(arg: T): T - {
- return arg;
- }
我们定义了泛型函数后,可以用两种方法使用。
- 1.传入所有的参数,包含类型参数:
- let output = identity<string>("myString"); // type of output will be 'string'
-
- 2.利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
- let output = identity("myString"); // type of output will be 'string'
- 如果我们想同时打印出arg的长度。 我们很可能会这样做:
- function loggingIdentity
(arg: T): T { - console.log(arg.length); // Error: T doesn't have .length
- return arg;
- }
- 记住,这些类型变量代表的是任意类型,所以使用这个函数的人可能传入的是个数字,而数字是没有 .length属性的。
- //-----------------------------------------------------------------------------
- 现在假设我们想操作T类型的数组而不直接是T。由于我们操作的是数组,所以.length属性是应该存在的。
- function loggingIdentity
(arg: T[]): T[] { - console.log(arg.length); // Array has a .length, so no more error
- return arg;
- }
-
- 我们也可以这样实现上面的例子
- function loggingIdentity
(arg: Array): Array { - console.log(arg.length); // Array has a .length, so no more error
- return arg;
- }
泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:
- function identity
(arg: T): T { - return arg;
- }
-
- let myIdentity:
(arg: T) => T = identity; -
- //也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。
- function identity
(arg: T): T { - return arg;
- }
-
- let myIdentity: (arg: U) => U = identity;
-
- //还可以使用带有调用签名的对象字面量来定义泛型函数:
- function identity
(arg: T): T { - return arg;
- }
-
- let myIdentity: {
(arg: T): T} = identity;
这引导我们去写第一个泛型接口了。拿上面的例子里的对象字面量作为一个接口:
- interface GenericIdentityFn
- {
-
(arg: T): T; - }
-
- function identity
(arg: T): T - {
- return arg;
- }
-
- let myIdentity: GenericIdentityFn = identity;
-
-
- 一个相似的例子:
- 如果我们想把泛型参数当作整个接口的一个参数,这样就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary<string>而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。
-
- interface GenericIdentityFn
- {
- (arg: T): T;
- }
-
- function identity
(arg: T): T - {
- return arg;
- }
-
- let myIdentity: GenericIdentityFn<number> = identity;
⚠️注意,我们的示例做了少许改动。 不再描述泛型函数,而是把非泛型函数签名作为泛型类型一部分。 当我们使用 GenericIdentityFn的时候,还得传入一个类型参数来指定泛型类型(这里是:number),锁定了之后代码里使用的类型。
除了泛型接口,我们还可以创建泛型类。 ⚠️注意无法创建 泛型枚举 和 泛型命名空间
泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。
- class GenericNumber
{ - zeroValue: T;
- add: (x: T, y: T) => T;
- }
-
- let myGenericNumber = new GenericNumber<number>();
- myGenericNumber.zeroValue = 0;
- myGenericNumber.add = function(x, y) { return x + y; };
-
- 没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。
- let stringNumeric = new GenericNumber<string>();
- stringNumeric.zeroValue = "";
- stringNumeric.add = function(x, y) { return x + y; };
-
- console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
⚠️注意:类有两部分:静态部分和实例部分。
泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
你应该会记得在 loggingIdentity例子中,我们想访问arg的length属性,但是编译器并不能证明每种类型都有length属性,所以就报错了。
- function loggingIdentity
(arg: T): T { - console.log(arg.length); // Error: T doesn't have .length
- return arg;
- }
为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:
- interface Lengthwise {
- length: number;
- }
-
- function loggingIdentity
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上,
- 因此我们需要在这两个类型之间使用约束。
- function getProperty(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使用泛型创建工厂函数时,需要引用构造函数的类类型。
- function create
(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!