青训营笔记 | TypeScript 类、泛型的使用实践Demo | 豆包MarsCode AI刷题

0 阅读4分钟

在 TypeScript 中,泛型 是核心特性之一,结合起来使用,可以显著增强代码的复用性和类型安全性。以下是 TypeScript 中类与泛型基本的使用方法和场景


1. 泛型基础

泛型(Generics)允许我们定义 类型参数,可以在类、接口或函数中灵活地适配不同的类型。

示例:简单泛型类

class Box<T> {
  private content: T;

  constructor(content: T) {
    this.content = content;
  }

  getContent(): T {
    return this.content;
  }

  setContent(content: T): void {
    this.content = content;
  }
}

// 使用
const stringBox = new Box<string>("Hello, TypeScript");
console.log(stringBox.getContent()); // 输出: "Hello, TypeScript"

const numberBox = new Box<number>(123);
console.log(numberBox.getContent()); // 输出: 123

说明

  • T 是一个占位符,代表类型参数,用户在实例化时决定其具体类型。
  • 使用泛型让 Box 可以适配不同类型,不需要为每种类型单独定义类。

2. 多个类型参数

可以定义多个类型参数,适用于需要处理多种类型关系的场景。

示例:键值对存储

class KeyValuePair<K, V> {
  constructor(public key: K, public value: V) {}

  display(): void {
    console.log(`Key: ${this.key}, Value: ${this.value}`);
  }
}

// 使用
const pair = new KeyValuePair<number, string>(1, "TypeScript");
pair.display(); // 输出: "Key: 1, Value: TypeScript"

const anotherPair = new KeyValuePair<string, boolean>("isDone", true);
anotherPair.display(); // 输出: "Key: isDone, Value: true"

说明

  • 使用多个类型参数 KV,让 KeyValuePair 同时支持键值对的灵活定义。
  • 在实际应用中,类似的场景常见于实现 Map 或缓存结构。

3. 泛型约束

有时,我们需要对泛型参数进行约束,要求它必须满足某些条件(如实现某个接口)。

示例:约束为特定接口

interface Lengthwise {
  length: number;
}

class LengthChecker<T extends Lengthwise> {
  checkLength(item: T): void {
    console.log(`Length: ${item.length}`);
  }
}

// 使用
const checker = new LengthChecker<{ length: number; name: string }>();
checker.checkLength({ length: 10, name: "Test" }); // 输出: "Length: 10"

// 错误示例: 类型不满足约束
// checker.checkLength(123); // Error: 类型“number”不满足约束“Lengthwise”。

说明

  • T extends Lengthwise 限制了 T 必须有 length 属性。
  • 这种约束提高了类型安全性,确保只有符合条件的类型才能被使用。

4. 泛型和静态成员

泛型参数是作用于类的实例级别,而不是静态成员级别。

静态属性不能使用泛型类型

示例:静态成员与泛型的区别

class StaticExample<T> {
  static staticProperty: string = "Static Property";

  instanceProperty: T;

  constructor(value: T) {
    this.instanceProperty = value;
  }
}

// 静态属性不能访问泛型类型
console.log(StaticExample.staticProperty); // 输出: "Static Property"

// 实例属性使用泛型
const example = new StaticExample<number>(42);
console.log(example.instanceProperty); // 输出: 42

说明

  • 泛型无法直接约束或作用于静态属性
  • 因为静态成员在类加载时初始化,而泛型类型是在实例化时确定。

5. 泛型类与接口的结合

可以用泛型类实现泛型接口,从而增强代码的灵活性。

示例:泛型类实现泛型接口

interface Repository<T> {
  add(item: T): void;
  getAll(): T[];
}

class InMemoryRepository<T> implements Repository<T> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  getAll(): T[] {
    return this.items;
  }
}

// 使用
const repo = new InMemoryRepository<string>();
repo.add("TypeScript");
repo.add("Generics");
console.log(repo.getAll()); // 输出: ["TypeScript", "Generics"]

说明

  • Repository 接口通过泛型 T 描述了方法的类型约束。
  • InMemoryRepository 实现该接口时具体化了类型。

6. 泛型与继承

泛型可以与继承结合,形成更复杂的类型约束。

示例:带约束的继承

class Animal {
  constructor(public name: string) {}
}

class Dog extends Animal {
  bark(): void {
    console.log("Woof!");
  }
}

class AnimalShelter<T extends Animal> {
  private animals: T[] = [];

  addAnimal(animal: T): void {
    this.animals.push(animal);
  }

  getAllAnimals(): T[] {
    return this.animals;
  }
}

// 使用
const dogShelter = new AnimalShelter<Dog>();
dogShelter.addAnimal(new Dog("Rex"));
console.log(dogShelter.getAllAnimals()); // 输出: [Dog { name: 'Rex' }]

说明

  • T extends Animal 限制了 T 必须是 Animal 或其子类。
  • 确保了 shelter 只能管理动物或其派生类的实例。

7. 泛型工具:Partial 和 Readonly

TypeScript 提供了一些内置工具类型,可结合泛型类提升灵活性。

示例:Partial 和 Readonly

class User {
  constructor(public id: number, public name: string) {}
}

// Partial
function updateUser(user: Partial<User>): User {
  return { id: user.id ?? 1, name: user.name ?? "Default" };
}
console.log(updateUser({ name: "Updated Name" })); // 输出: { id: 1, name: "Updated Name" }

// Readonly
class ReadonlyRepository<T> {
  private items: ReadonlyArray<T>;

  constructor(items: T[]) {
    this.items = items;
  }

  getAll(): ReadonlyArray<T> {
    return this.items;
  }
}

// 使用
const readonlyRepo = new ReadonlyRepository<User>([
  new User(1, "Alice"),
  new User(2, "Bob"),
]);
console.log(readonlyRepo.getAll()); // 输出: [User { id: 1, name: 'Alice' }, User { id: 2, name: 'Bob' }]

说明

  • Partial 创建可选类型的泛型工具,非常适合更新操作。
  • Readonly 防止对象的属性被修改,提高数据安全性。

总结

  • 泛型的核心作用 是让代码更加通用和灵活,特别适用于需要多样化类型适配的场景。
  • 类型约束 提高了类型安全性,确保泛型的实际类型符合期望。
  • 实践场景:如数据存储、管理结构(队列、堆栈、集合)、工具函数(Partial、Readonly)等。