Typescript 泛型

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

const getArray = <T>(value: T, times: number = 5): T[] => {
  return new Array(times).fill(value);
};

定义函数之前,使用<>符号定义了一个泛型变量 T,这个 T 在这次函数定义中就代表某一种类型,它可以是基础类型,也可以是联合类型等高级类型。定义了泛型变量之后,你在函数中任何需要指定类型的地方使用 T 都代表这一种类型。比如当我们传入 value 的类型为数值类型,那么返回的数组类型T[]就表示number[]。现在我们再来调用一下这个 getArray 函数:

getArray<number[]>([1, 2], 3).forEach(item => {
  console.log(item); // 执行三次,返回[1,2]
});
getArray<number>(2, 2).forEach(item => {
  console.log(item); // 执行2次,返回2
});

调用时可以省略指定泛型变量,TypeScript 会根据你传入函数的 value 值的类型进行推断:

getArray(2, 3).forEach(item => {
  console.log(item.length); // 类型“number”上不存在属性“length”
});

指定多个泛型变量

这个例子中,我们定义了两个泛型变量T和U。第一个参数的类型为 T,第二个参数的类型为 U,最后函数返回一个二维数组,函数返回类型我们指定是一个元素类型为[T, U]的数组。所以当我们调用函数,最后遍历结果时,遍历到的每个元素都是一个第一个元素是数值类型、第二个元素是字符串类型的数组。

const getArray = <T, U>(param1: T, param2: U, times: number): [T, U][] => {
  return new Array(times).fill([param1, param2]);
};
getArray(1, "a", 3).forEach(item => {
  console.log(item[0].length); // error 类型“number”上不存在属性“length”
  console.log(item[1].toFixed(2)); // error 属性“toFixed”在类型“string”上不存在
});

泛型函数类型

// ex1: 简单定义
const getArray: <T>(arg: T, times: number) => T[] = (arg, times) => {
  return new Array(times).fill(arg);
};
// ex2: 使用类型别名
type GetArray = <T>(arg: T, times: number) => T[];
const getArray: GetArray = <T>(arg: T, times: number): T[] => {
  return new Array(times).fill(arg);
};
// ex3: 使用接口定义
interface GetArray {
  <T>(arg: T, times: number): T[];
}
const getArray: GetArray = <T>(arg: T, times: number): T[] => {
  return new Array(times).fill(arg);
};
// ex4: 可以把接口中泛型变量提升到接口最外层,这样接口中所有属性和方法都能使用这个泛型变量
interface GetArray<T> {
  (arg: T, times: number): T[];
  tag: T;
}
const getArray: GetArray<number> = <T>(arg: T, times: number): T[] => {
  // error 不能将类型“{ <T>(arg: T, times: number): T[]; tag: string; }”分配给类型“GetArray<number>”
  // 属性“tag”的类型不兼容。
  return new Array(times).fill(arg);
};
getArray.tag = "a"; // 不能将类型“"a"”分配给类型“number”
getArray("a", 1); // 不能将类型“"a"”分配给类型“number”

泛型约束

泛型约束就是使用一个类型和extends对泛型进行约束

interface ValueWithLength {
  length: number;
}
const getLength = <T extends ValueWithLength>(param: T): number => {
  return param.length;
};
getLength("abc"); // 3
getLength([1, 2, 3]); // 3
getLength({ length: 3 }); // 3
getLength(123); // error 类型“123”的参数不能赋给类型“ValueWithLength”的参数


// 这里我们使用让K来继承索引类型keyof T,你可以理解为keyof T相当于一个由泛型变量T的属性名构成的联合类型,在这里 K 就被约束为了只能是"a"或"b",所以当我们传入字符串"c"想要获取对象obj的属性"c"时就会报错。
const getProp = <T, K extends keyof T>(object: T, propName: K) => {
  return object[propName];
};
const obj = { a: "aa", b: "bb" };
getProp(obj, "c"); // 类型“"c"”的参数不能赋给类型“"a" | "b"”的参数