泛型 ( Generics ) 是指在定义函数、接口或类等变量时,先不指定具体类型,而是在使用时再指定类型的一种特性。
举个栗子🌰:
编写一个函数,接收一个值 value 和次数 length (长度),返回一个 value 值重复 length 次的数组,如 generateArray('x', 3); 返回 ['x', 'x', 'x']
JS 实现:

  1. const generateArray = (value, length) => {
  2. /*
  3. Array(length).join(',').split(',').map(() => value);
  4. Array(length).join(',').split(',').fill(value);
  5. Array.from({length}, () => value);
  6. Array(length).fill(value);
  7. */
  8. return Array(length).fill(value);
  9. }
  10. const arr = generateArray('x', 5);
  11. console.log(arr);

TS 实现:

  1. function generateArray(value: any, length: number): any[] {
  2. return Array(length).fill(value);
  3. }
  4. const arr = generateArray('x', 5);
  5. console.log(arr);

上述代码能够实现基本功能需求,但对于 TypeScript 而言,它的类型推论及提示完全不够( 返回数组的类型永远是 : any[] ),如果要期望函数能根据从传入的 value 类型,进而推论出返回的数组类型,就需要使用泛型:
TS 泛型实现:

function generateArray<T>(value: T, length: number): T[] {
  return Array(length).fill(value);
}

const arr = generateArray('x', 5);
console.log(arr);

其中函数名 generateArray 后的 <T> 表示声明指代任意传入类型参数,后面的 value: T 表示具体传入类型, : T[] 表示返回传入类型的数组,其中 : T[] 可用泛型表达式 Array<T> 代替。

1 多个类型参数

定义泛型时可定义多个类型参数,如元组交换顺序:

function swapTuple<F, S>(tuple: [F, S]): [S, F] {
  return [tuple[1], tuple[0]];
}

const tuple_1: [string, number] = ['1', 2];
const tuple_2 = swapTuple(tuple_1);

2 泛型约束

使用泛型的类型参数时,函数内部并不知道传入的类型,进而不知道它的方法和属性,产生编译错误:

function isIterable<T>(arg: T): T {
  /**
   * ERROR:
   * 类型“T”上不存在属性“length”。
   * 类型“T”上不存在属性“size”。
   */
  if (arg.length || arg.size) {
    return arg;
  }
}

要解决这个问题可以使用接口 ( Interfaces ) 对泛型 ( Generics ) 的形状进行约束,使其传入的类型参数必须符合接口类型

interface cusIterable {
  length?: number,
  size?: number,
}

// 泛型的输入类型 T 继承于接口 cusIterable
function isIterable<T extends cusIterable>(arg: T): T {
  if (arg.length || arg.size) {
    return arg;
  }
}

同样的,泛型能预测属性是否存在:

function hasProp<O, K extends keyof O>(obj: O, p: K): boolean {
  return typeof obj[p] !== "undefined"
}

let o = {
  name: "Tom",
  age: 18
}
hasProp(o, "name");
// ERROR: 类型“"gender"”的参数不能赋给类型“"name" | "age"”的参数。
hasProp(o, "gender");

3 泛型接口

前面知道,可以使用接口来约束函数的形状,当然也可以使用含有泛型的接口来约束函数的形状:

// 声明泛型接口
interface IGenerateArray {
  <T>(value: T, length: number): Array<T>
}

let generateArray: IGenerateArray;
generateArray = function<T>(value: T, length: number): T[] {
  return Array(length).fill(value);
}

4 泛型类

与泛型接口类似,泛型也能用于约束类的声明:

// 声明泛型类
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

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

5 泛型参数的默认类型

自 TypeScript 2.3 之后,可以用 <T = type> 的格式,为泛型的参数类型指定默认类型,在没有为泛型传入参数类型,也没有推论出参数类型时生效 (产生默认提示)。

function generateArray<T = string>(value: T, length: number): Array<T> {
  return Array(length).fill(value);
}

6 交叉类型

交叉类型常用于合并对象,格式: t1 & t2 (与联合类型不同)

function crossType<F extends object, T extends object>(o1: F, o2: T): F & T {
  const r = {} as F & T
  for (const k in o1) {
    r[k] = o1[k] as any
  }
  for (const k in o2) {
    if (!r.hasOwnProperty(k)) {
      r[k] = o2[k] as any
    }
  }
  return r;
}
class Human {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
interface Man {
  gender: string,
  introduce: (favorite: string) => string
}
class Male implements Man {
  gender = "男";
  introduce(favorite: string): string {
    return `性别:${this.gender},爱好:${favorite}。`;
  }
}
class Female implements Man {
  gender = "女";
  introduce(favorite: string): string {
    return `性别:${this.gender},爱好:${favorite}。`;
  }
}

const m = crossType(new Human("杰克"), new Male());
console.log(m.name, m.introduce("唱歌"));
const f = crossType(new Human("罗丝"), new Female());
console.log(f.name, f.introduce("跳舞"));