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

一.指定函数参数类型

单个泛型

案例:创建一个指定长度的数组,同时将每一项都填充一个默认值,根据参数不同,处理结果不同,入参和返回值有映射关系

// 这样写 此时入参和返回值类型就固定了,无法再进行扩展,想需要复用时就不方便
const getArray = (times: number, val: string): string[] => {
  let res = [];
  for (let i = 0; i < times; i++) {
    res.push(val);
  }
  return res;
};
getArray(3, "a");
getArray(3, 1); // 当val改为 其他类型时,就会报错,类型不匹配,无法复用

使用泛型改写后得代码:会根据传入的类型进行推导

const getArray = <T>(times: number, val: T): T[] => {
  let res = [];
  for (let i = 0; i < times; i++) {
    res.push(val);
  }
  return res;
};
getArray(3, "a"); // const getArray: <string>(times: number, val: string) => string[]
getArray(3, 1); // const getArray: <number>(times: number, val: number) => number[]
getArray(3, true); //const getArray: <boolean>(times: number, val: boolean) => boolean[]

多个泛型

定义泛型的时候,可以一次定义多个类型参数:案例:元组交换

function swap(tuple: [number, string]): [string, number] {
  return [tuple[1], tuple[0]];
}
swap([7, "seven"]);
swap([7, 8]); // 当改为 其他类型时,就会报错,类型不匹配,无法复用

使用泛型改写后得代码:

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}
swap([7, "seven"]);
swap([7, 8]); // 可以正确推导类型

二.函数标注的方式

类型定义比较长时,建议使用interfacetype关键词单独声明,函数中多用type

类型别名 type

类型别名不能被继承和实现

type ISwap = <T, U>(aaa: [T, U]) => [U, T];
const swap: ISwap = (tuple) => [tuple[1], tuple[0]];
swap([7, "seven"]);

接口 interface

interface ISwap {
  <T, U>(tuple: [T, U]): [U, T];
}
const swap: ISwap = (tuple) => [tuple[1], tuple[0]];
swap([7, "seven"]);

*案例分析:

const forEach = <T>(arr: T[], callback: (item: T, idx: number) => void) => {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
};

forEach(["a", "b", "c"], function (item, idx) {
  console.log(item);
});

进行类型的抽离,先抽离 callback 类型,在抽离函数类型
需要注意的是, 写在等号前和后是有区别的:

  • <T> 写在等号前:使用接口的时候确定的类型,也就是回调函数并没有真正执行时,就可以知道参数类型
  • <T> 写在等号后:在调用函数的时候确定了类型
type ICallback<T> = (item: T, idx: number) => void; // 使用接口的时候确定的类型,也就是回调函数并没有真正执行时,就可以知道参数类型
type IForEach = <T>(arr: T[], callback: ICallback<T>) => void; // 在调用函数的时候确定了类型
const forEach: IForEach = (arr, callback) => {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
};

forEach(["a", "b", "c"], function (item, idx) {
  console.log(item);
});

三.默认泛型

可以为泛型中的类型参数指定默认类型。也就是说给泛型增加了默认值,当使用泛型时没有在代码中直接指定类型参数,从实参中也无法推测出时,默认类型就会起作用。

type Union<T = string> = T | number;

type t1 = Union; // type t1 = string | number
type t2 = Union<boolean>; // type t2 = number | boolean

四.泛型约束

约束传入的泛型类型 A extends B, A 是 B 的子类型

function handle<T extends number | string>(val: T): T {
  return val;
}
let r1 = handle(123);
let r2 = handle("abc");
let r3 = handle(true); // 报错

泛型必须包含某些属性,length

interface IWithLength {
  length: number;
}
function getLen<T extends IWithLength>(val: T) {
  return val.length;
}
getLen((a: number, b: string) => {});

约束索引的签名,返回泛型中指定属性,若访问了泛型中不存在属性,就是报错提示

function getVal<T extends object, U extends keyof T>(obj: T, key: U) {
  return obj[key];
}
getVal({ a: 1, b: 2, c: 3 }, "c");

五.泛型接口使用

对象中可以使用泛型 常见的就是描述接口的返回值,将复杂的接口,通过泛型拆分开

interface ApiResponse<T = any> {
  code: number;
  data: T;
  message?: string;
}
interface LoginRes {
  token: string;
}
function toLogin(): ApiResponse<LoginRes> {
  return {
    code: 200,
    data: {
      token: "Bearer token",
    },
  };
}
let r = toLogin();
r.data.token;

六.类中的泛型

创建实例时提供类型进行泛型约束

class MyList<T extends number | string> {
  private arr: T[] = [];
  add(val: T) {
    this.arr.push(val);
  }
  getMax(): T {
    let arr = this.arr;
    let max = arr[0];
    for (let i = 0; i < arr.length; i++) {
      let cur = arr[i];
      cur > max ? (max = cur) : void null;
    }
    return max;
  }
}
let list = new MyList();
list.add(1);
list.add(true);