# Generics 泛型

假如:需要一个函数,返回传入它的内容。不使用泛型时,它会是这样的:

// 案例:
// 这个函数接受两个参数。第一个参数为任意类型的值,第二个参数为数值类型的值,默认为 5。
const getArray = (value: any, times: number = 5): any[] => {
  return new Array(times).fill(value);
};


getArray([1], 2).forEach(item => {
  console.log(item.length);
});

getArray(2, 3).forEach(item => {
  console.log(item.length);  // 报错
});

我们调用了两次这个方法,使用 forEach 方法遍历得到的数组,在传入 forEach 的函数中获取当前遍历到的数组元素的 length 属性。

上面例子中第二次调用getArray的返回值每个元素应该是数值类型,遍历这个数组时我们获取数值类型的length属性也没报错,因为这里item的类型是any。

上面的示例,定时any时,丢失了一些信息:

  • 传入的类型与返回的类型应该是相同的;

  • 如果我们传入一个数字,我们只知道任何类型的值都有可能被返回;

于是此时需要一种方法使返回值的类型与传入参数的类型是相同的。

# 定义

泛型指的是,在定义函数、接口或类的时候不预先指定数据类型,而在使用时再指定类型的特性。

作用:泛型可以提升应用的可重用性,如使用其创建组件,则可以使组件可以支持多种数据类型。

拿上面这个例子中的逻辑来举例:

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

这个 T 在这次函数定义中就代表某一种类型,它可以是基础类型,也可以是联合类型等高级类型。

现在我们再来调用一下这个 getArray 函数:

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

在调用 getArray 的时候,在方法名后面使用<>传入了我们的泛型变量 T 的类型number[],那么在定义 getArray 函数时使用 T 指定类型的地方,都会使用number[]指定。

# 泛型变量

泛型变量,这是一种特殊的变量,只用于表示类型而不是值。

但是,你也可以省略这个<number[]>,TypeScript 会根据你传入函数的 value 值的类型进行推断:

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

由于我们在定义getArray时,给value也定义了T类型,那么,传入实参时,会同时将实参的类型传递给类型变量 T。

应用场景:交换两个数组元素

不使用泛型会丢失数据类型:

function swap(tuple) {
  return [tuple[1], tuple[0]]
}

使用泛型后,不仅会保有类型推断,还可以直接调用实例的方法:

function swapGeneric<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}

const result2 = swapGeneric(['string', 0.123])

// ts的类型推断系统能够明确得知第一个元素会是数值,而第二个元素会是字符串
result2[0].toFixed(2)
result2[1].toLocaleUpperCase()