TypeScript(六) 泛型

199 阅读2分钟

泛型

泛型(Generics)是一种编程语言特性,允许在定义函数、类、接口等时使用占位符来表示类型,而不是具体的类型。泛型是一种在编写可重用、灵活且类型安全的代码时非常有用的功能。使用泛型的主要目的是为了处理不特定类型的数据,使得代码可以适用于多种数据类型而不失去类型检查。

  • 代码重用: 可以编写与特定类型无关的通用代码,提高代码的复用性。
  • 类型安全: 在编译时进行类型检查,避免在运行时出现类型错误。
  • 抽象性: 允许编写更抽象和通用的代码,适应不同的数据类型和数据结构。

泛型标识符

在泛型中,通常使用一些约定俗成的标识符,比如常见的 T(表示 Type)、UV 等,但实际上你可以使用任何标识符。关键是使得代码易读和易于理解,所以建议在泛型类型参数上使用描述性的名称,以便于理解其用途。

标识符 T

代表 "Type",是最常见的泛型类型参数名。

function identity<T>(arg: T): T {
  return arg;
}
标识符 K, V

用于表示键(Key)和值(Value)的泛型类型参数。

interface KeyValuePair<K, V> {
  key: K;
  value: V;
}
标识符 E

用于表示数组元素的泛型类型参数。

function printArray<E>(arr: E[]): void {
  arr.forEach((item) => console.log(item));
}
标识符 R

用于表示函数返回值的泛型类型参数。

function getResult<R>(value: R): R {
  return value;
}
标识符 U, V

通常用于表示第二、第三个泛型类型参数。

function combine<U, V>(first: U, second: V): string {
  return `${first} ${second}`;
}

泛型函数

使用泛型来创建一个 lodash 中的 map 的函数。

function map<E, R>(arr: E[], fn: (arg: E) => R): R[] {
  return arr.map(fn);
}

map<number, string>([1, 3, 4], (number) => `${number}`);

以上例子中,map 是一个泛型函数,它有两个类型参数 ER。含义是,原始数组的类型为 E[],对该数组的每个成员执行一个处理函数 fn,将类型 E 转成类型 R,那么就会得到一个类型为 R[] 的数组。

泛型接口

可以使用泛型来定义接口,使接口的成员能够使用任意类型。

interface A<T, U, V> {
  point: T;
  line: U;
  surface: V;
}

interface Point {
  x: number;
  y: number;
}

interface Line {
  length: string;
}

let l: A<Point, Line, number> = {
  point: { x: 100, y: 100 },
  line: { length: "1000" },
  surface: 100000,
};

这里定义了一个泛型接口 A,它有三个类型参数 TUV。然后,使用这个泛型接口创建了一个对象 l,其中 pointPoint 类型,lineLine 类型,surfacenumber 类型。

泛型类

泛型也可以应用于类的实例变量和方法:

class Pair<T, U> {
  private key: T;
  private value: U;

  constructor(key: T, value: U) {
    this.key = key;
    this.value = value;
  }

  getValue(): U {
    return this.value;
  }

  getKey(): T {
    return this.key;
  }

  getObject(): { key: T; value: U } {
    return {
      key: this.key,
      value: this.value,
    };
  }
}

const pair = new Pair<string, boolean>("flag", true);

class A extends Pair<string, unknown> {}
class B extends Pair<unknown, unknown> {}

const obj = new A(1, 2); // error 类型“number”的参数不能赋给类型“string”的参数。
const obj1 = new A("name", "rinvay");

const o = new B(1, 2);
const o1 = new B("name", "rinvay");

在这个例子中,Pair 是一个泛型类,使用 T, U 表示泛型类型,构造函数和方法都可以使用泛型类型 T, U

创建 Pair 实例 pair 时,需要同时给出类型参数 T, U 和类参数 key, value 的值。

class Aclass B分别继承了 class Pair 在继承泛型类时也需要给出 T, U 的值。通过实例化 A, B,我们分别创建了存储对象的实例 obj、obj1o、o1。创建实例的时候参数需要和继承声明的类型保持一致。

泛型约束

如果想限制泛型的类型范围,可以使用泛型约束,以限制可以在运行时使用的具体类型。

interface LengthSet {
  length: number;
}

function logMaxLength<T extends LengthSet>(arr: T[], maxLength: number): void {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].length > maxLength) {
      console.log(`${arr[i]} is greater than ${maxLength}`);
    }
  }
}

const names: string[] = ["Alice", "Bob", "Charlie", "David"];
/**
 * Outputs
 * Alice is greater than 3
 * Charlie is greater than 3
 * David is greater than 3
 */
logMaxLength(names, 3);

在这个例子中,我们定义了一个 LengthSet 接口,该接口包含一个 length 属性。然后,我们创建了一个 logMaxLength 函数,该函数接受一个类型为 T 的数组和一个 maxLength 参数。通过使用泛型约束,我们限制了 T 必须是一个 LengthSet 类型,这意味着数组中的每个元素都必须有一个 length 属性。这样,我们就可以在函数中安全地访问和比较这些属性的值。

泛型与默认值

范型参数可以设置默认值,在使用过程中,如果没有给出类型参数的值,就会使用该默认值。

function defaultValue<T = string>(arg: T): T {
  return arg;
}

let result2 = defaultValue(42);
let res = defaultValue("rinvay");

T = string 表示类型参数的默认值是 string。调用 defaultValue() 时,如果咩有给 T 的值,会使用默认类型 string