TypeScript 类、泛型的使用实践记录 | 豆包MarsCode AI刷题

150 阅读4分钟

TypeScript 中的泛型是用于编写可重用的、灵活的代码的一种重要工具。泛型允许你在类、接口、函数等中定义类型参数,使得你能够在类型层面上进行抽象,进而提高代码的灵活性和可重用性。使用泛型时,你可以通过类型约束来确保类型安全。以下是泛型在 TypeScript 中的常见用法及实践场景。

1. 基本的泛型用法

泛型可以应用于函数、类和接口。最常见的用途是定义函数,允许函数接收不同类型的参数,并根据参数类型返回相应的结果。

函数中的泛型

// 泛型函数
function identity<T>(value: T): T {
  return value;
}

// 使用泛型函数
let result1 = identity(5);      // result1 类型为 number
let result2 = identity("hello"); // result2 类型为 string

在这个例子中,identity 函数是一个泛型函数,它接受一个类型为 T 的参数,并返回类型为 T 的结果。T 是类型变量,在调用 identity 时会根据传入的参数类型来推断。

类中的泛型

泛型也可以用于类,允许类的实例具有灵活的类型。

class Box<T> {
  value: T;
  
  constructor(value: T) {
    this.value = value;
  }

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

const numberBox = new Box(123);  // numberBox 类型为 Box<number>
const stringBox = new Box("hello"); // stringBox 类型为 Box<string>

console.log(numberBox.getValue());  // 输出: 123
console.log(stringBox.getValue());  // 输出: hello

在这里,Box 是一个泛型类,类型 T 在创建实例时被指定,getValue 方法返回该类型的值。

2. 泛型约束

通过使用泛型约束,可以限制泛型的类型范围,确保传入的类型符合特定的结构或条件。常见的约束方式包括使用 extends 关键字来指定类型约束。

数字类型的约束

function add<T extends number>(a: T, b: T): T {
  return a + b;
}

let sum = add(10, 20); // sum 类型为 number
// add("10", "20"); // 错误: 类型 "string" 不满足类型 "number"

在这个例子中,T extends number 表示泛型 T 必须是 number 类型,这确保了函数 add 只接受数字类型的参数。

对象类型的约束

interface Person {
  name: string;
  age: number;
}

function greet<T extends Person>(person: T): string {
  return `Hello, ${person.name}`;
}

const person = { name: "Alice", age: 30 };
console.log(greet(person)); // 输出: Hello, Alice

在这里,泛型 T 被约束为 Person 类型或其子类型,因此 greet 函数只能接受符合 Person 类型结构的对象。

3. 使用泛型进行组合

泛型的强大之处在于可以组合使用,从而增强代码的灵活性。可以将泛型与接口、类型别名等结合,进行更复杂的数据结构设计。

泛型接口

interface Pair<T, U> {
  first: T;
  second: U;
}

const pair: Pair<number, string> = { first: 1, second: "hello" };

在这个例子中,Pair 是一个泛型接口,它有两个类型参数 TU,分别表示 firstsecond 的类型。实例 pair 被指定为 Pair<number, string>

泛型与函数式编程结合

function map<T, U>(arr: T[], transform: (item: T) => U): U[] {
  return arr.map(transform);
}

const numbers = [1, 2, 3];
const strings = map(numbers, num => num.toString()); // strings 类型为 string[]

map 是一个泛型函数,它接受一个类型为 T 的数组,并且接受一个转换函数 transform,该函数将数组中的每个元素转换为类型 U 的元素。返回的结果是一个 U[] 类型的数组。

4. 默认泛型类型

TypeScript 允许为泛型提供默认类型,这样在调用时,如果没有指定类型,TypeScript 会自动使用默认类型。

function createArray<T = number>(length: number, value: T): T[] {
  return new Array(length).fill(value);
}

let arr1 = createArray(3, 5); // arr1 类型为 number[]
let arr2 = createArray(3, "hello"); // arr2 类型为 string[]

在这里,createArray 函数的泛型类型 T 默认值是 number,如果调用时没有指定类型,TypeScript 会默认使用 number 类型。

5. 泛型在复杂场景中的应用

泛型与联合类型

泛型可以和联合类型结合使用,从而创建更加灵活的类型约束。

function combine<T, U>(a: T, b: U): T | U {
  return a;
}

const result1 = combine(1, "hello"); // result1 类型为 number | string
const result2 = combine(true, [1, 2, 3]); // result2 类型为 boolean | number[]

泛型与递归类型

TypeScript 中的泛型还可以支持递归类型定义,使得类型更加动态和灵活。

interface TreeNode<T> {
  value: T;
  left?: TreeNode<T>;
  right?: TreeNode<T>;
}

const tree: TreeNode<number> = {
  value: 10,
  left: { value: 5 },
  right: { value: 15 }
};

在这个例子中,TreeNode 是一个递归类型,它包含了 leftright 子节点,每个节点的类型为 TreeNode<T>

总结

TypeScript 的泛型提供了极大的灵活性,可以在类型层面进行抽象,提高代码的可重用性和类型安全性。通过约束泛型类型,可以确保数据的结构和类型安全。在复杂场景下,泛型还可以和联合类型、递归类型等结合使用,进一步增强类型的表达力。

使用泛型时的一些关键要点包括:

  • 类型推导:TypeScript 会根据传入的参数自动推导泛型类型。
  • 类型约束:使用 extends 关键字为泛型添加约束,确保类型符合预期。
  • 默认类型:可以为泛型提供默认类型,使得泛型调用更加简洁。

通过这些实践,开发者可以编写更加灵活且类型安全的代码,适应不同的应用场景。