简介

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

  1. function createArray<T>(length: number, value: T): Array<T> {
  2. let result: T[] = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }
  8. // 指定T为string
  9. createArray<string>(3, 'x'); // ['x', 'x', 'x']
  10. // ⭐当然也可以不指定,让类型推断自动推算:
  11. createArray(3, 'x'); // 推断出T为string类型

多个类型参数

定义泛型的时候,可以一次定义多个类型参数:

  1. function swap<T, U>(tuple: [T, U]): [U, T] {
  2. return [tuple[1], tuple[0]];
  3. }
  4. swap([7, 'seven']); // ['seven', 7]

泛型约束extends

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法,这时候可以用extends来进行泛型约束:

  1. interface Lengthwise {
  2. length: number;
  3. }
  4. // extends 约束了传参泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。
  5. function loggingIdentity<T extends Lengthwise>(arg: T): T {
  6. console.log(arg.length);
  7. return arg;
  8. }
  9. loggingIdentity(7); // 报错:类型“number”的参数不能赋给类型“Lengthwise”的参数。

多个类型参数之间也可以互相约束:

  1. // T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。
  2. function copyFields<T extends U, U>(target: T, source: U): T {
  3. for (let id in source) {
  4. target[id] = (<T>source)[id];
  5. }
  6. return target;
  7. }
  8. let x = { a: 1, b: 2, c: 3, d: 4 };
  9. copyFields(x, { b: 10, d: 20 });

泛型约束extends还可以用于三元表达式,很多泛型工具函数都是利用了联合类型在extends时条件分发功能,如Exclude

  1. interface Fish {
  2. type: '鱼'
  3. }
  4. interface Bird {
  5. type: '鸟'
  6. }
  7. interface Swim {
  8. swim(): void
  9. }
  10. interface Fly {
  11. fly(): void
  12. }
  13. type MyType<T> = T extends Fish ? Swim : Fly
  14. type MyType2 = MyType<Bird> // Fly
  15. // ⭐如果传入的是联合类型,会进行条件的分发,有一个符合就返回一个
  16. type MyType3 = MyType<Bird | Fish> // Swim | Fly
  17. // 交叉类型&不具备分发
  18. type MyType3 = MyType<Bird & Fish> // Swim 其实Bird & Fish交叉类型为never,never可以赋给任意类型,所以可以赋给Fish,所以返回Swim
  19. type Exclude<T, k> = T extends k ? never : T // 从联合类型T中剔除掉某个k的类型,不是取差集
  20. type Extract<T, k> = T extends k ? T : never // 从T中取出k的类型,取交集
  21. type NonNullable<T> = T extends null | undefined ? never : T // 去除null和undefined

泛型推断infer

infer必须在extends条件语句中才可以使用,表示待推断的类型变量。

  1. // ⭐获取函数返回值类型
  2. // 前面的extends是限制传入的参数T必须是函数类型,后面的extends是为了使用infer(infer必须在extends语句中使用)
  3. type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; // 这里infer R放在返回值后面表示推断返回值类型
  4. // ⭐获取函数参数类型
  5. // 如果infer R放在参数后面,就代表推断参数类型,也就是Parameters的实现:
  6. type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
  7. // 例子:
  8. function getType(x: number, y: string) {
  9. return { name: 'xjp', age: 12 }
  10. }
  11. type getReturnType = ReturnType<typeof getType> // { name: string; age: number; }
  12. type getParameters = Parameters<typeof getType> // [x: number, y: string]
  13. // ⭐获取构造函数的参数类型
  14. type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
  15. // ⭐获取实例类型(实例就是new Class,而new Class就是执行类的构造函数constructor,所以获取实例类型就是获取类构造函数的返回值类型)
  16. type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
  17. // 例子
  18. class Person {
  19. constructor(name: string) { }
  20. }
  21. type getConstructorParameters = ConstructorParameters<typeof Person> // [name: string]
  22. type getInstanceType = InstanceType<typeof Person> // Person。所以类作为类型实际上就是实例的类型,而typeof class则是完整的类的类型(包括prototype等)

泛型接口

可以使用接口的方式来定义一个函数需要符合的形状,当然也可以使用含有泛型的接口来定义函数的形状:

  1. interface CreateArrayFunc {
  2. <T>(length: number, value: T): Array<T>;
  3. }
  4. let createArray: CreateArrayFunc = function <T>(length: number, value: T): Array<T> {
  5. let result: T[] = [];
  6. for (let i = 0; i < length; i++) {
  7. result[i] = value;
  8. }
  9. return result;
  10. };
  11. // 进一步,把泛型参数提前到接口名上:
  12. interface CreateArrayFunc<T> {
  13. (length: number, value: T): Array<T>;
  14. }
  15. let createArray: CreateArrayFunc<any> = function <T>(length: number, value: T): Array<T> {
  16. let result: T[] = [];
  17. for (let i = 0; i < length; i++) {
  18. result[i] = value;
  19. }
  20. return result;
  21. };
  22. createArray(3, "x"); // ['x', 'x', 'x']

泛型类

与泛型接口类似,泛型也可以用于类的类型定义中:

  1. class GenericNumber<T> {
  2. zeroValue: T;
  3. add: (x: T, y: T) => T;
  4. }
  5. let myGenericNumber = new GenericNumber<number>();
  6. myGenericNumber.zeroValue = 0;
  7. myGenericNumber.add = function(x, y) { return x + y; };

泛型参数的默认类型

当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用:

  1. function createArray<T = string>(length: number, value: T): Array<T> {
  2. let result: T[] = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }

误用泛型

1、泛型仅作用在单个参数

  1. declare function foo<T>(arg: T): void;
  2. // 上面的泛型完全没有必要使用,因为它仅用于单个参数的位置,使用如下方式可能更好:
  3. declare function foo(arg: any): void;

2、仅返回使用

  1. declare function parse<T>(name: string): T;
  2. // 上面泛型只在一个地方被使用了,它并没有在成员之间提供约束 T