当前位置:  首页>> 技术小册>> TypeScript入门指南

泛型是 TypeScript 中的重要特性,它可以让我们在编写代码时更加灵活、安全和可复用。

泛型的概念很简单:就是定义一种通用的类型或函数,可以用来适应不同类型的数据。在实际编程中,泛型的应用非常广泛,包括函数、类、接口等方面。本文将通过具体的代码示例,介绍 TypeScript 中泛型的基本概念、应用场景以及注意事项,希望可以帮助读者更好地理解和使用泛型。


1、泛型基础

泛型可以理解为一种类型占位符,它可以用来表示任何类型。在 TypeScript 中,我们可以使用尖括号(<>)来定义泛型,例如:

  1. function identity<T>(arg: T): T {
  2. return arg;
  3. }

在这个例子中,identity 函数使用了泛型 T,它代表任意类型。函数的参数和返回值都是类型为 T 的值。通过这种方式,我们可以在函数中使用任何类型的值,而不需要对每种类型都编写一份代码。

当我们调用 identity 函数时,需要指定泛型的具体类型。例如:

  1. let output1 = identity<string>("hello"); // output1 的类型为 string
  2. let output2 = identity<number>(1); // output2 的类型为 number

我们也可以不指定泛型的类型,让 TypeScript 根据参数的类型自动推导出泛型的类型:

  1. let output3 = identity("hello"); // output3 的类型为 string
  2. let output4 = identity(1); // output4 的类型为 number

以上代码中,TypeScript 根据参数的类型自动推导出了泛型 T 的类型。

2、泛型函数的应用

泛型函数可以用于解决不同类型的数据处理问题。例如,我们可以编写一个函数,用来返回一个数组的第一个元素:

  1. function getFirstElement<T>(arr: T[]): T | undefined {
  2. return arr[0];
  3. }

在这个例子中,getFirstElement 函数使用了泛型 T,它代表数组元素的类型。函数的参数是一个类型为 T 的数组,函数的返回值类型是 T 或 undefined。通过这种方式,我们可以用这个函数来获取任意类型的数组的第一个元素:

  1. let arr1 = [1, 2, 3];
  2. let firstElement1 = getFirstElement<number>(arr1); // firstElement1 的类型为 number
  3. let arr2 = ["a", "b", "c"];
  4. let firstElement2 = getFirstElement<string>(arr2); // firstElement2 的类型为 string

以上代码中,我们分别传入了一个 number 类型的数组和一个 string 类型的数组,调用了 getFirstElement 函数,并通过泛型指定了数组元素的类型。函数的返回值类型根据泛型的类型自动推导出来。

3、泛型类的应用

泛型不仅可以应用于函数中,还可以应用于类和接口中。下面我们来看一个泛型类的例子:

  1. class Pair<T, U> {
  2. constructor(public first: T, public second: U) {}
  3. swap(): Pair<U, T> {
  4. return new Pair(this.second, this.first);
  5. }
  6. }

在这个例子中,我们定义了一个 Pair 类,它使用了两个泛型类型 T 和 U,代表两个不同的类型。类的构造函数需要两个参数,一个类型为 T,一个类型为 U,并将它们分别保存在 first 和 second 属性中。Pair 类还定义了一个 swap 方法,用于将 first 和 second 属性交换位置,并返回一个新的 Pair 对象。

我们可以使用 Pair 类来创建不同类型的对象:

  1. let pair1 = new Pair<string, number>("hello", 123); // pair1 的类型为 Pair<string, number>
  2. let pair2 = new Pair<number, boolean>(123, true); // pair2 的类型为 Pair<number, boolean>
  3. let swappedPair1 = pair1.swap(); // swappedPair1 的类型为 Pair<number, string>
  4. let swappedPair2 = pair2.swap(); // swappedPair2 的类型为 Pair<boolean, number>

以上代码中,我们分别创建了两个 Pair 对象,一个使用了 string 和 number 类型,一个使用了 number 和 boolean 类型。我们还调用了 swap 方法,并通过泛型指定了交换后新对象的类型。

4、泛型约束

在使用泛型时,有时我们需要对泛型进行一些约束,以确保它符合某些要求。例如,我们需要对泛型进行一些操作,但这些操作只能应用于特定类型。在这种情况下,我们可以使用泛型约束。

下面是一个例子,它演示了如何使用泛型约束:

  1. interface Lengthwise {
  2. length: number;
  3. }
  4. function logLength<T extends Lengthwise>(arg: T): void {
  5. console.log(arg.length);
  6. }

在这个例子中,我们定义了一个接口 Lengthwise,它包含了一个 length 属性,类型为 number。然后我们编写了一个函数 logLength,它使用了泛型 T,并对泛型进行了约束,要求它必须是一个实现了 Lengthwise 接口的类型。

通过这种方式,我们可以保证 logLength 函数只会接收实现了 Lengthwise 接口的类型。我们可以传入一个字符串或数组等类型的数据,因为它们都实现了 length 属性:

  1. logLength("hello"); // 输出 5
  2. logLength([1, 2, 3]); // 输出 3
  3. 但如果我们传入一个不实现 `Lengthwise` 接口的类型,比如一个数字,则会产生编译错误:
  4. ```typescript
  5. logLength(123); // 编译错误:数字类型没有 length 属性

另一个例子是使用泛型约束实现工厂模式。我们可以定义一个工厂接口 Factory,它包含一个方法 create,用于创建类型为 T 的对象。然后我们编写一个泛型函数 createObject,使用泛型约束 T extends Factory,表示泛型 T 必须实现 Factory 接口。函数内部调用 create 方法创建一个对象。

以下是代码示例:

  1. interface Factory<T> {
  2. create(): T;
  3. }
  4. function createObject<T extends Factory<T>>(factory: T): T {
  5. return factory.create();
  6. }

我们可以定义一个 PersonFactory 类,实现了 Factory 接口,并重写了 create 方法,用于创建一个 Person 对象:

  1. class Person {
  2. constructor(public name: string, public age: number) {}
  3. }
  4. class PersonFactory implements Factory<Person> {
  5. create(): Person {
  6. return new Person("Tom", 18);
  7. }
  8. }

然后我们可以调用 createObject 函数,传入一个 PersonFactory 对象,创建一个 Person 对象:

  1. let person = createObject(new PersonFactory()); // person 的类型为 Person
  2. console.log(person.name); // 输出 "Tom"
  3. console.log(person.age); // 输出 18

以上例子中,我们使用了泛型约束 T extends Factory,要求泛型 T 必须实现 Factory 接口。然后我们定义了一个 PersonFactory 类,实现了 Factory 接口,并重写了 create 方法,用于创建一个 Person 对象。最后我们调用 createObject 函数,传入一个 PersonFactory 对象,创建了一个 Person 对象。

小结
TypeScript 中的泛型为我们提供了一种通用的方法,可以在编写代码时,不必提前指定具体的类型,而是将类型作为参数进行传递。泛型可以应用于函数、类、接口等不同的场景,使我们的代码更加灵活和通用。在使用泛型时,我们可以通过泛型约束来限制泛型的类型范围,以确保泛型符合某些要求。


该分类下的相关小册推荐: