TypeScript - 泛型类

你好,未来的编程超级巨星们!今天,我们将深入TypeScript泛型类的精彩世界。如果你是编程新手,不用担心;我会像过去几年里指导无数学生一样,一步步引导你。所以,拿起你最喜欢的饮料,舒服地坐好,让我们一起开始这段冒险!

TypeScript - Generic Classes

泛型类

什么是泛型类?

想象你在一个冰淇淋店,但不是在选择口味,而是在挑选数据类型。这就是泛型类的本质!它们允许我们创建灵活、可重用的组件,可以处理不同的数据类型,同时不牺牲类型安全。

让我们从一个简单的例子开始:

class Box<T> {
private content: T;

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

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

在这个例子中,Box 是一个泛型类。<T> 就像是一个占位符,我们稍后会指定类型。这就像告诉冰淇淋店:“我下单时会决定口味!”

让我们分解一下:

  • class Box<T>: 这声明了一个名为 Box 的泛型类,有一个类型参数 T
  • private content: T: 我们说 content 将是类型 T,无论 T 是什么。
  • constructor(value: T): 构造函数接受一个类型为 T 的值。
  • getValue(): T: 这个方法返回一个类型为 T 的值。

现在,让我们看看如何使用这个类:

let numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 输出: 42

let stringBox = new Box<string>("你好,TypeScript!");
console.log(stringBox.getValue()); // 输出: 你好,TypeScript!

这很酷吧?我们使用了同一个 Box 类来存储数字和字符串。这就像有一个魔法的盒子,可以装任何你放进去的东西,但仍然记得它具体装的是什么类型的物品!

多个类型参数

有时,一个类型参数不够。让我们创建一个更复杂的例子,使用多个类型参数:

class Pair<T, U> {
private first: T;
private second: U;

constructor(first: T, second: U) {
this.first = first;
this.second = second;
}

getFirst(): T {
return this.first;
}

getSecond(): U {
return this.second;
}
}

这个 Pair 类可以持有两个可能是不同类型的数据。这就像有一个双人冰淇淋蛋筒,每个球可以是不同的口味!

让我们使用我们的 Pair 类:

let pair = new Pair<string, number>("年龄", 30);
console.log(pair.getFirst());  // 输出: 年龄
console.log(pair.getSecond()); // 输出: 30

泛型约束

有时,我们想要限制可以在我们的泛型类中使用哪些类型。我们可以通过使用约束来实现这一点。这就像说:“你可以选择任何冰淇淋口味,只要它不是太辣的!”

interface Lengthwise {
length: number;
}

class LengthChecker<T extends Lengthwise> {
checkLength(obj: T): string {
return `长度是: ${obj.length}`;
}
}

在这个例子中,T extends Lengthwise 意味着 T 必须是一个有 length 属性的类型。让我们使用它:

let stringChecker = new LengthChecker<string>();
console.log(stringChecker.checkLength("你好")); // 输出: 长度是: 2

let arrayChecker = new LengthChecker<number[]>();
console.log(arrayChecker.checkLength([1, 2, 3])); // 输出: 长度是: 3

// 这会导致错误:
// let numberChecker = new LengthChecker<number>();
// 类型 'number' 不满足约束 'Lengthwise'。

在泛型类中实现泛型接口

现在,让我们通过在泛型类中实现泛型接口来提升我们的技能。这就像为不同类型的冰淇淋创建一个食谱(接口)!

首先,让我们定义一个泛型接口:

interface Repository<T> {
getById(id: number): T;
save(item: T): void;
}

这个 Repository 接口定义了处理数据存储和检索的类的契约。现在,让我们用泛型类实现这个接口:

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

getById(id: number): T {
return this.items[id];
}

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

我们的 GenericRepository 类实现了 Repository 接口。它可以处理任何类型 T。让我们使用它:

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

let userRepo = new GenericRepository<User>();

userRepo.save({ name: "Alice", age: 30 });
userRepo.save({ name: "Bob", age: 25 });

console.log(userRepo.getById(0)); // 输出: { name: "Alice", age: 30 }
console.log(userRepo.getById(1)); // 输出: { name: "Bob", age: 25 }

在这个例子中,我们创建了一个用于 User 对象的仓库。但我们泛型实现的美丽之处在于,我们同样可以轻松地为任何其他类型创建仓库!

方法表

以下是我们所涵盖方法的便捷表格总结:

方法 描述 示例
constructor(value: T) 创建泛型类的新实例 new Box<number>(42)
getValue(): T 返回泛型类中存储的值 numberBox.getValue()
getFirst(): T 返回对中的第一个值 pair.getFirst()
getSecond(): U 返回对中的第二个值 pair.getSecond()
checkLength(obj: T): string 检查对象(有约束)的长度 stringChecker.checkLength("你好")
getById(id: number): T 通过 ID 从仓库中检索项目 userRepo.getById(0)
save(item: T): void 将项目保存到仓库 userRepo.save({ name: "Alice", age: 30 })

就这样,大家!我们一起穿越了TypeScript泛型类的领域,从基本的盒子到复杂的仓库。记住,熟能生巧,所以不要害怕尝试这些概念。谁知道呢?你可能会创造出编程界的下一个大事件!下次见,快乐编程!

Credits: Image by storyset